MongoDB使用save()方法报VersionError版本错误如何解决
问题根因
两类报错的触发逻辑明确:
save()抛出的VersionError来自Mongoose默认的乐观并发控制(版本校验)机制:通过findById查询文档时,实例会携带当前版本标记(默认存储在__v字段),若查询完成到执行save()的间隙内,有其他请求修改了同一份文档并更新了版本号,当前持有的旧版本实例执行保存时会因版本匹配失败抛错。对数组字段(比如代码中的history)的增删操作会触发Mongoose更严格的版本校验,是这类报错的高发场景。- 调用
update时抛出的Performing an update on the path '_id' would modify the immutable field '_id'错误,是因为更新时直接传入了查询得到的完整文档对象,对象携带的_id字段被MongoDB识别为待修改字段,而_id是文档创建后不可变更的保留字段,因此触发报错。
可行解决方案
方案1:使用原子更新替代find+save(最推荐)
直接调用MongoDB的原子更新方法,不需要先查文档再修改保存,从根源避免并发版本冲突,同时只要不传入整个文档对象、只传更新操作符,就不会触发_id不可修改的报错。
针对给出的业务逻辑,对应代码如下:
// 组装要追加的history记录 const historyItem = { user: req.user._id, status: status, createdAt: new Date() }; // 组装更新内容 const updateData = { $push: { // $position:0 等价于unshift,把记录插到数组头部 history: { $each: [historyItem], $position: 0 } }, $set: { status: status, comment: comment } }; // 处理误拒标记的逻辑 if (is_false_reject) { updateData.$set.isFalseReject = is_false_reject; updateData.$set.falseRejectedAt = new Date(); } // 执行原子更新,new:true返回更新后的文档 const updatedFile = await File.findByIdAndUpdate( req.params.id, updateData, { new: true, runValidators: true } );
注意:原子更新全程只传入要修改的字段和操作符,不会携带
_id字段作为更新目标,完全规避_id不可修改的报错。
方案2:保留find+save逻辑,增加重试机制
如果业务逻辑复杂,必须先查询文档做额外计算再保存,可以在捕获到VersionError时增加重试逻辑:重新拉取最新版本的文档,应用修改后再次尝试保存,一般设置2-3次重试即可覆盖绝大多数并发冲突场景。
示例代码逻辑:
const MAX_RETRY = 3; let retryCount = 0; let fileDoc; while (retryCount < MAX_RETRY) { try { fileDoc = await File.findById(req.params.id); fileDoc.history.unshift({ user: req.user._id, status: status, createdAt: new Date() }); if (is_false_reject) { fileDoc.isFalseReject = is_false_reject; fileDoc.falseRejectedAt = new Date(); } fileDoc.status = status; fileDoc.comment = comment; await fileDoc.save(); break; // 保存成功跳出循环 } catch (err) { if (err.name === 'VersionError' && retryCount < MAX_RETRY -1) { retryCount++; continue; // 版本错误则重试 } throw err; // 其他错误或者重试耗尽直接抛错 } }
方案3:临时关闭版本校验(不推荐)
如果确认业务场景不存在并发写覆盖的风险,可以在调用save时手动关闭版本校验,但该方式会丢失Mongoose提供的并发保护,可能出现数据互相覆盖的问题,非必要不使用:
await fileDoc.save({ versionKey: false });
内容的提问来源于stack exchange,提问作者Vanshika
相关产品推荐
相关产品推荐

