MongoDB多用户场景下第二及后续用户无法更新保存数据问题
问题描述
开发后端接口时遇到异常:业务逻辑为创建File记录并同步更新关联User文档的Dataset字段,当系统仅存在1个用户时,数据创建、更新流程可正常执行;但当系统存在2个及以上用户时,数据无法正常创建或更新。相关接口实现代码如下:
const createFile = async (req, res, next) => { const errors = validationResult(req); if (!errors.isEmpty()) { return next( new HttpError('Invalid inputs passed, please check your data.', 422) ); } const { userId, Dataset } = req.body; const createdFile = new File({ userId, Dataset, }); let user; try { user = await User.findById(userId); } catch (err) { const error = new HttpError( 'Creating place failed, please try again 1', 500 ); return next(error); } if (!user) { const error = new HttpError('Could not find user for provided id', 404); return next(error); } try { const sess = await mongoose.startSession(); sess.startTransaction(); await createdFile.save({ session: sess }); user.Dataset.push(createdFile); await user.save({ session: sess }); await sess.commitTransaction(); } catch (err) { const error = new HttpError( 'Creating place failed, please try again.2', 500 ); return next(error); } res.status(201).json({ files: createdFile }); };
接口运行时抛出的错误信息如下:
Error: User validation failed: _id: Error, expected `_id` to be unique. Value: `62c661c629d1cb99768efd05` at ValidationError.inspect (C:\Users\acit\Desktop\FYP Code\FYP Code\backend2\node_modules\mongoose\lib\error\validation.js:48:26) at internal/per_context/primordials.js:23:32 at formatValue (internal/util/inspect.js:783:19) at inspect (internal/util/inspect.js:337:10) at formatWithOptionsInternal (internal/util/inspect.js:2016:40) at formatWithOptions (internal/util/inspect.js:1898:10) at console.value (internal/console/constructor.js:323:14) at console.log (internal/console/constructor.js:358:61) at createFile (C:\Users\acit\Desktop\FYP Code\FYP Code\backend2\controllers\files-controller.js:102:13) at processTicksAndRejections (internal/process/task_queues.js:93:5) { errors: { _id: ValidatorError: Error, expected `_id` to be unique. Value: `62c661c629d1cb99768efd05` at validate (C:\Users\acit\Desktop\FYP Code\FYP Code\backend2\node_modules\mongoose\lib\schematype.js:1321:13) at C:\Users\acit\Desktop\FYP Code\FYP Code\backend2\node_modules\mongoose\lib\schematype.js:1297:24 at processTicksAndRejections (internal/process/task_queues.js:93:5) { properties: [Object], kind: 'unique', path: '_id', value: new ObjectId("62c661c629d1cb99768efd05"), reason: undefined, [Symbol(mongoose:validatorError)]: true } }, _message: 'User validation failed' }
故障原因
报错核心是User集合触发了_id唯一约束冲突,说明执行user.save()时,Mongoose没有识别到这是对已存在User文档的更新操作,反而将其判定为新文档的插入操作,尝试写入的_id已经在集合中存在,最终触发报错。
具体触发逻辑:
- 代码在事务外部查询得到User文档,这个文档没有和后续创建的事务session绑定,属于事务上下文外的游离文档
- 当在事务中传入session执行
user.save()时,Mongoose无法匹配到事务上下文内的文档状态,在多用户场景下缓存状态不一致,就会误将更新操作判定为新文档插入 - 单用户场景下Mongoose文档缓存刚好能匹配到原有文档状态,因此不会触发该问题,存在多个用户时缓存冲突概率大幅升高,故障稳定复现
- 额外逻辑缺陷:如果Dataset字段设计为File集合的关联引用字段,直接push完整File文档对象也会导致数据写入不符合预期,额外增加校验冲突概率
修复方案
- 将User文档的查询操作移到事务内部,查询时绑定当前事务的session,保证文档和事务上下文一致,让Mongoose正确识别这是更新操作而非新插入
- 明确Dataset字段的存储逻辑:如果是关联File集合的引用数组,只需要push File文档的
_id即可,不需要传入完整文档对象;如果是嵌套子文档数组,再传入完整文档结构 - 补全事务的异常回滚、session关闭逻辑,避免事务资源泄漏
修复后的核心代码段:
const createFile = async (req, res, next) => { const errors = validationResult(req); if (!errors.isEmpty()) { return next( new HttpError('Invalid inputs passed, please check your data.', 422) ); } const { userId, Dataset } = req.body; let sess; try { sess = await mongoose.startSession(); sess.startTransaction(); // 事务内查询用户,绑定session const user = await User.findById(userId).session(sess); if (!user) { throw new HttpError('Could not find user for provided id', 404); } const createdFile = new File({ userId, Dataset }); await createdFile.save({ session: sess }); // 引用场景下只存储文件ID,嵌套子文档场景可替换为createdFile user.Dataset.push(createdFile._id); await user.save({ session: sess }); await sess.commitTransaction(); res.status(201).json({ files: createdFile }); } catch (err) { if (sess) { await sess.abortTransaction(); } const error = new HttpError( err.statusCode ? err.message : 'Creating file failed, please try again.', err.statusCode || 500 ); return next(error); } finally { if (sess) { await sess.endSession(); } } };
内容的提问来源于stack exchange,提问作者newbies
相关产品推荐
相关产品推荐

