WriteConflict 是 MongoDB 事务中因多个事务并发修改同一数据而由 WiredTiger 引擎主动中止事务的协作机制,并非代码错误;它常见于高并发更新同批文档,需应用层捕获错误码 112 并在外层循环重试,每次新建 session、控制重试次数与退避延迟,同时优化索引、拆分事务、优先使用原子操作降低冲突。

WriteConflict 是什么,为什么它总在事务里冒出来
WriteConflict 不是代码写错了,而是 MongoDB 在事务执行过程中发现多个事务试图修改同一份数据——底层 WiredTiger 引擎检测到写冲突,主动中止其中一个事务。它常见于高并发更新同一批文档(比如订单状态、库存扣减),尤其当事务里有 findAndModify、updateOne 带条件更新、或多次读-改-写操作时。
关键点在于:这不是失败,是预期中的协作机制。MongoDB 不会自动重试,也不抛出传统“错误”,而是让事务以 WriteConflict 错误退出,交由应用决定怎么处理。
捕获并重试 WriteConflict 的标准姿势
必须在事务外层做重试控制,不能依赖驱动内置重试(MongoDB 驱动默认不为事务开启自动重试)。典型结构是用循环 + try/catch 包裹 session.withTransaction() 调用。
- 重试次数建议设为 3–5 次,再高大概率说明设计有问题(比如锁粒度太粗)
- 每次重试前加
await new Promise(r => setTimeout(r, Math.random() * 10)),避免雪崩式重试 - 不要在重试逻辑里复用旧的
session,每次重试都应新建session.startTransaction() - 注意:重试时整个事务逻辑要可重入——不能依赖外部状态,也不能在事务外提前修改了变量值
示例片段(Node.js):
let attempt = 0;
const maxAttempts = 3;
<p>while (attempt < maxAttempts) {
const session = client.startSession();
try {
await session.withTransaction(async () => {
const coll = session.getDatabase('db').collection('orders');
await coll.updateOne({ _id: orderId, status: 'pending' }, { $set: { status: 'processing' } });
// 其他操作...
});
break; // 成功则跳出
} catch (error) {
if (error.name === 'MongoError' && error.code === 112) { // 112 是 WriteConflict 的 error code
attempt++;
if (attempt >= maxAttempts) throw error;
await new Promise(r => setTimeout(r, Math.random() * 10));
} else {
throw error;
}
} finally {
await session.endSession();
}
}哪些操作特别容易触发 WriteConflict
不是所有更新都会高频冲突,但以下模式几乎必踩坑:
-
findOneAndUpdate或findAndModify作用于无索引字段——导致全表扫描+行锁升级为范围锁 - 事务内多次读取同一文档再更新(比如先
findOne,再根据结果updateOne),中间窗口期被其他事务抢占 - 更新条件太宽泛,如
{ status: 'active' }匹配大量文档,锁住的文档数远超实际需要 - 事务持续时间过长(>1s),比如里面混了 HTTP 请求、文件 IO 或复杂计算——锁持有时间拉长,冲突概率指数上升
比重试更治本的三个调整方向
频繁重试只是兜底,真正降低 WriteConflict 要从数据模型和访问模式入手:
- 确保更新条件字段有支撑索引,尤其是复合查询里的前导字段。用
explain("executionStats")看是否走了索引、扫描了多少文档 - 把大事务拆小:比如“扣库存+生成订单+发消息”拆成两个事务(库存扣减独立,订单创建另起),减少锁竞争面
- 考虑用原子操作替代读-改-写:比如用
$inc直接扣减库存,而不是先查再算再更新;用$push+$each批量追加而非循环updateOne
最常被忽略的一点:WriteConflict 错误本身不带冲突发生在哪一行的信息,得靠日志 + db.currentOp({ "secs_running": { "$gt": 1 } }) 定位长事务,再结合业务逻辑反推热点文档。










