使用findOneAndUpdate更新两个不同ObjectId用户数组时仅单条更新生效
问题根因定位
你现在遇到的双写仅单条成功的问题,是三个高频开发坑叠在一起导致的:
- 核心错误:
findOneAndUpdate的第一个参数必须是查询过滤规则对象,用来指定要匹配哪条文档。你现在直接把接口拿到的用户ID字符串当第一个参数传,新版本的MongoDB驱动、Mongoose根本不会隐式把这个字符串当成匹配_id字段的条件——相当于查询规则完全失效,要么匹配不到任何文档,触发的报错被异步回调静默吞掉,要么靠upsert建了个完全不符合预期的脏文档,根本没写到你要找的那两个用户对象上。 - 执行链路不可靠:你现在用纯回调的方式写两个异步操作,既没有捕获每一步的报错,也没有做流程串联。只要其中某一步抛异常,后续操作根本不会正常执行,自然就出现只成功一个的现象。
- 没有原子性保障:两个独立的更新操作之间没有兜底机制,一旦执行到一半出现网络波动、数据库连接中断,就会出现某一个用户的数组更新成功、另一个没更新成功的脏数据问题。
修正实现方案
首先必须先把查询参数的错误改对,这是能跑通的基础:
// 你的原代码(错误写法) Users.findOneAndUpdate(userToFollow, { $push: ... }) // 正确写法:明确指定匹配规则,比如按用户的_id字段匹配目标文档 Users.findOneAndUpdate({ _id: userToFollow }, { $push: ... })
改完传参问题后,再用async/await把两个操作的执行链路串起来,加上统一错误捕获,保证每一步的执行结果都能被感知:
// 接口处理函数示例 exports.follow = async (req, res) => { try { const userToFollow = req.body.following; const userThatFollows = req.body.follower; // 顺序执行两个更新,每一步完成后再执行下一步,抛错就直接进入catch分支 await Users.findOneAndUpdate( { _id: userToFollow }, { $push: { followers: userThatFollows } }, { new: true, upsert: true } ); await Users.findOneAndUpdate( { _id: userThatFollows }, { $push: { following: userToFollow } }, { new: true, upsert: true } ); res.json({ code: 200, message: "关注关系写入成功" }); } catch (err) { // 所有环节的报错都会在这里被捕获,不会被静默吞掉 res.status(500).json({ code: 500, message: "关注关系写入失败", detail: err.message }); } }
可选优化
- 避免数组重复数据:如果不想出现同一个用户被重复push到followers/following数组里,可以把
$push换成$addToSet,从根源上防止重复值。 - 原子性保障:如果你的MongoDB是4.0及以上版本,推荐用多文档事务把两个更新操作包成原子流程,不管哪一步执行失败,都会回滚到操作前的状态,彻底避免数据不一致的问题:
const session = await mongoose.startSession(); try { session.startTransaction(); // 两个更新操作都要传入当前session标识 await Users.findOneAndUpdate( { _id: userToFollow }, { $addToSet: { followers: userThatFollows } }, { new: true, upsert: true, session } ); await Users.findOneAndUpdate( { _id: userThatFollows }, { $addToSet: { following: userToFollow } }, { new: true, upsert: true, session } ); await session.commitTransaction(); } catch (e) { // 任意一步失败就回滚所有操作 await session.abortTransaction(); // 错误处理逻辑 } finally { // 最后关闭session释放资源 await session.endSession(); }
内容的提问来源于stack exchange,提问作者Arham Farman
相关产品推荐
相关产品推荐

