Mongoose 8.1.3中可靠传递文档旧版本至findOneAndUpdate的post钩子
解决Mongoose findOneAndUpdate钩子中并发安全的新旧文档对比问题
你当前用全局变量传递旧文档的方式确实存在并发风险——多个请求同时执行时,全局变量会被覆盖,导致post钩子拿到的旧数据和当前更新不匹配。以下是两种更可靠的实现方式:
1. 利用查询实例的自定义属性存储旧数据
Mongoose的pre('findOneAndUpdate')钩子中,this指向当前查询的专属实例,每个请求的查询实例是独立的,不会互相干扰。可以把旧文档数据存在这个实例的自定义属性中,在post钩子中直接取用。
x.pre('findOneAndUpdate', async function(next) { try { // 将旧文档存入当前查询实例的自定义属性 this._previousDoc = await this.model.findOne(this.getQuery()) .select({ logs: 0, updatedAt: 0, _id: 0 }) .lean() .exec(); next(); } catch (e) { next(e); // 正确传递错误给Mongoose处理流程 } }); x.post('findOneAndUpdate', async function(document) { try { const previous = this._previousDoc; const current = document.toObject(); const added = addedDiff(previous, current); // 执行你的变更处理逻辑 } catch (e) { // 按需处理错误 } });
这个方案从根源上避免了全局变量的并发冲突,因为每个请求的旧数据都绑定在自己的查询实例上,完全隔离。
2. 配合乐观锁保证数据一致性
如果担心pre钩子查询到旧文档后,在执行更新前有其他请求修改了该文档(导致旧数据过期),可以加上MongoDB的乐观锁机制,确保更新的是pre钩子中查到的版本:
x.pre('findOneAndUpdate', async function(next) { try { const previousDoc = await this.model.findOne(this.getQuery()) .select({ logs: 0, updatedAt: 0, _id: 0, __v: 1 }) // 带上版本号 .lean() .exec(); this._previousDoc = previousDoc; // 更新查询条件,只匹配当前版本的文档 this.where({ __v: previousDoc.__v }); // 自增版本号,确保下一次更新必须匹配新的版本 this.set({ __v: previousDoc.__v + 1 }); next(); } catch (e) { next(e); } }); // post钩子代码同方案1
这样如果在pre钩子之后有其他请求修改了文档,当前的findOneAndUpdate会返回null(因为版本号不匹配),你可以在后续逻辑中处理这种并发冲突场景。
内容的提问来源于stack exchange,提问作者richnwilson
相关产品推荐
相关产品推荐

