mongodb事务中重复键错误(如11000)会强制回滚整个事务,因其原子性设计不允许部分提交;有效应对方式包括:拆出冲突操作、单条try/catch捕获、findoneandupdate原子upsert或事务外预检+事务内写入。

为什么重复键错误会让整个事务回滚
MongoDB 的事务是原子性的,一旦任意操作抛出未捕获的错误(包括 duplicate key),整个事务就会被中止并自动回滚。这不是“可配置选项”,而是设计行为——commitTransaction 不会执行,所有已写入的变更都会撤销。
常见触发场景:并发插入相同 _id 或唯一索引字段;批量写入前没做去重或预检查;用 insertMany 但部分文档违反唯一约束。
-
duplicate key错误码固定为11000(单字段唯一)或11001(复合唯一) - 即使你在事务里先
findOne判断存在性,仍可能因并发导致“判断时不存在、插入时已存在”(典型 check-then-act 竞态) - 事务内无法用
upsert: true绕过重复键问题——updateOnewithupsert在键冲突时仍报错,不会静默忽略
用 writeConcern + ordered: false 隔离失败项
在事务内调用 insertMany 或 bulkWrite 时,把 ordered: false 设为 false,能让非关键错误(如重复键)不中断后续操作,但注意:这**不能防止事务回滚**——只要任何操作失败,事务仍会整体失败。所以真正有效的是:把可能冲突的操作拆出事务,或改用带错误捕获的单条写入。
-
bulkWrite中设{ ordered: false }后,重复键错误会出现在结果的writeErrors数组里,其余成功项仍生效——但前提是**不在事务中**;事务内该选项无效 - 若坚持在事务内处理,只能用循环 +
try/catch对每条insertOne单独包裹,捕获11000错误后跳过,继续下一条 - 务必检查
error.code === 11000而不是只看error.name === 'MongoServerError',避免误吞其他错误
用 findAndModify 替代 insert + 判断逻辑
对单文档场景,用 findOneAndUpdate 配合 upsert: true 和空更新体,能原子性地“有则更新、无则插入”,天然规避重复键问题,且不依赖事务。
db.collection.findOneAndUpdate(
{ _id: "abc" },
{ $set: { name: "test" } },
{ upsert: true, returnDocument: "after" }
)
- 该操作本身是原子的,无需事务包装
- 如果业务必须写多字段且需保证一致性(比如同时更新 A 和 B 字段),而 A 是唯一键,B 是普通字段,仍建议把 A 的唯一性保障交给索引,B 的更新用
findOneAndUpdate实现 - 不要用
find+if !exists then insert,这是竞态根源;findOneAndUpdate是服务器端原子操作
事务外预检 + 事务内确认写入
当必须用事务且涉及多集合写入(例如 A 表插入 + B 表关联更新),又担心某条插入触发重复键,可行做法是:先在事务外批量查重,筛掉冲突项,再把干净数据送入事务。
- 查重用
find({ _id: { $in: ids } })或countDocuments,比逐条findOne更高效 - 查重与事务开始之间仍有极短窗口,所以最终写入仍要保留
try/catch捕获11000,只是概率大幅降低 - 若查重发现大量冲突,说明上游数据质量或去重逻辑有问题,应前置修复,而不是靠事务兜底
真正难处理的不是单点重复,而是多个唯一约束交叉作用(比如 email 唯一、phone 唯一、username 唯一),这时事务内任何一条失败都会拖垮全部——得按约束维度分批校验,或改用应用层分布式锁+幂等 ID 控制写入节奏。











