WriteConflict(错误码112)是WiredTiger MVCC快照校验导致的正常并发冲突,需通过精确_id更新、新建session重试、拆分事务边界及避免读写分离来应对。

跨集合更新必须用事务,但WriteConflict不是事务的错
WriteConflict(错误码112)在跨集合场景下是正常现象,不是配置或驱动问题——它源于WiredTiger对修改文档的MVCC快照校验:只要两个事务并发更新同一文档(哪怕只改一个字段),后提交者必然被中止。你不能靠“调大超时”或“换驱动版本”绕过它,只能接受并处理。
事务内更新必须严格限定文档粒度
常见错误是用模糊条件触发批量锁,比如updateMany({ status: "pending" }),这会让事务实际锁定几十甚至上百个文档,极大提高冲突概率。正确做法是:
- 所有
updateOne或findOneAndUpdate必须带精确_id或唯一索引字段(如orderId),避免范围扫描 - 跨集合操作前先用
findOne查出目标文档的_id,再用该_id发起更新,不复用原始查询条件 - 检查每个集合是否已为事务常用查询字段建了支持的索引,缺失索引会导致WiredTiger扫描扩大、隐式锁更多文档
重试逻辑必须新建session且不可依赖闭包状态
Spring或Node.js里常见的错误是把session对象或业务变量(如计数器、时间戳)放在事务函数外层闭包里。重试时这些值不会刷新,导致逻辑错乱或重复提交。必须做到:
- 每次重试都调用
client.startSession()新建session,不能复用上一轮的session - 事务函数体内部不读取外部可变变量;所有输入参数(如
orderId)必须从重试循环入口重新传入 - 捕获异常时只认
error.code === 112或错误消息含"WriteConflict",其他错误(如11000唯一键冲突)也要单独处理 - 退避必须随机化:
setTimeout(..., Math.random() * 50),否则高并发下会形成重试风暴
比重试更关键的是拆分事务边界
WriteConflict频发,往往说明事务粒度太大。比如“扣库存 + 改订单状态 + 记日志”全塞进一个事务,其实日志写入完全可以异步。优先考虑:
- 把非强一致性操作(如通知、审计日志)移出事务,用最终一致性方案补偿
- 批量跨集合更新拆成小批次,每批独立事务,失败只影响局部
- 单集合内高频更新场景,直接放弃事务,改用
findOneAndUpdate+version字段乐观锁(version必须是NumberInt(1),不能是字符串) - 确认所有shard、config server、mongos都运行4.2+且
featureCompatibilityVersion设为"4.2",缺一不可
最容易被忽略的是:事务内findOne读操作也绑定快照,别人一改它就读到旧值,后续更新必然冲突。所以读-改-写链条里,读和写必须用同一个原子操作完成,不要拆。


















