最新下载
热门教程
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
在MongoDB事务中如何实现基于乐观锁的并发控制_配合version字段校验
时间:2026-06-19 08:58:58 编辑:袖梨 来源:一聚教程网
findOneAndUpdate 本身不开启事务,但通过在 filter 中显式匹配 version 字段并配合 $inc 原子更新,可实现单文档乐观锁;matchedCount === 0 是版本冲突的唯一可靠信号,需重试并重新读取最新文档。
别在事务里套乐观锁——它俩根本不是一回事,硬凑反而让并发更差。
findOneAndUpdate 带 version 条件 ≠ 事务
MongoDB 的 findOneAndUpdate 本身不开启事务,也不加锁,但它天然具备单文档原子性。你写 { _id: docId, version: 5 } 并配合 $inc: { version: 1 },MongoDB 就会把「检查旧 version 是否还存在」和「升版+更新业务字段」打包成一次原子写操作。事务在这里纯属冗余:事务会在 WiredTiger 层对文档加写锁,高并发下容易排队、拖慢吞吐,甚至触发 write conflict 错误被自动中止。
真正起作用的是 filter 中显式带上 version 字段匹配,而不是包裹一层 session.startTransaction()。
updateOne + version 校验失败时 matchedCount === 0 才是正牌信号
很多人用 updateOne 后只看 modifiedCount 或 result.value === null,这不可靠:
-
modifiedCount只表示“字段值真变了”,比如你$set: { status: 'done' },但 status 原来就是 'done',它就为 0 —— 这跟版本冲突无关 -
result.value是findOneAndUpdate特有的返回字段,updateOne根本不返回它 - 唯一能确认“我读到的状态是否还有效”的,是
result.matchedCount === 0
这个值为 0,说明在你读出 version 之后、发起 update 之前,该文档已被别人更新,version 不再匹配。此时必须重试,不能跳过。
重试逻辑里最容易漏掉的三件事
写了 while 循环 retry,线上还是丢更新?问题不在 retry 本身,而在上下文没重置干净:
- 每次重试前必须重新
findOne拿最新文档,不能复用第一次读出的version和业务字段——否则你是在拿过期快照反复撞墙 - 如果更新前调了外部服务(如发短信、扣第三方账户),得确保这些操作幂等;否则重试会重复扣款、重复发通知
- 别只检查
matchedCount === 0,还要捕获result.lastErrorObject?.code === 11000(唯一键冲突),它可能和乐观锁失败同时发生
version 字段初始化和类型必须严格
version 不是装饰字段,它是乐观锁的命门:
- 插入首条文档时,必须显式设
version: 0或version: 1,不能留null、undefined或空字符串——否则后续所有$eq匹配都失效 -
version必须是数字(NumberInt或NumberLong),不能是字符串"1"或ObjectId;字符串比较在 BSON 里是字典序,"10" 会导致逻辑错乱 - 千万别在
$set里手动赋值version: 2——这绕过了原子性,且并发写可能把别人刚升的 2 覆盖回去
最常被忽略的其实是驱动行为:Node.js Driver 4.x+ 默认 returnDocument: 'before',你要显式设为 'after' 才能拿到升版后的最新快照。这个细节不处理,重试时连最新 version 都拿不到。
相关文章
- 明末渊虚之羽防具有哪些排名 07-02
- 如何获取和平精英皮肤照片 07-02
- 空洞骑士丝之歌如何获取制造金属 07-02
- 鱼骨头螃蟹阵容如何搭配 07-02
- 战魂旅人玩法是什么 07-02
- 无限暖暖祝你幸福发饰如何获取 07-02