mongodb批量写入不保证跨文档原子性,仅单文档操作天然原子;需跨文档原子性时必须显式使用事务,并正确绑定会话,否则bulkwrite等操作仍为独立执行。

MongoDB 的批量写入操作本身**不保证跨文档原子性**,但单文档级别天然原子。是否“原子”,取决于你用的是哪一层批量机制、是否开启事务、以及操作范围。
bulkWrite() 默认不跨文档原子
调用 db.collection.bulkWrite() 时,即使传入 100 条 updateOne 或 insertOne,每条操作仍独立执行。某条失败(如唯一键冲突),其余默认继续(无序)或中断(有序),但不会回滚已成功的部分。
- 有序模式下:第 5 条出错 → 前 4 条已提交,后 95 条不执行
- 无序模式下:第 5 条出错 → 前 4 条和后 95 条中能成功的都提交,错误汇总返回
- 无论哪种,MongoDB 不提供“全部成功才提交,任一失败全回滚”的语义 —— 这不是事务,只是批量发送
需要跨文档原子性?必须用事务
只有显式开启多文档事务,才能让一批写入(哪怕跨集合)具备 ACID 原子性。事务要求:副本集或分片集群、驱动支持、手动管理会话。
- 先调用
session.startTransaction() - 在该
session上执行所有bulkWrite()、updateOne()、deleteMany()等操作 - 最后
session.commitTransaction()或session.abortTransaction() - 注意:事务有 60 秒默认超时,大批量操作容易触发
TransactionTooOld错误
$push + $each 是单文档原子,不是批量原子
像 {$push: {tags: {$each: ["a","b","c"]}}} 这种更新,是对一个文档的数组字段做原子追加 —— 它属于单文档原子操作范畴,和“批量写入”无关。它快、安全,但只影响一个 _id 对应的文档。
- 不要误以为给 100 个文档发 100 个这样的操作,就等于“批量原子”
- 这 100 次操作彼此完全独立,中间可能被其他写入插入、覆盖或干扰
- 真正想一次改多个文档且要原子?只能靠事务包住这 100 次操作
C 驱动里 bulk 操作不自动关联事务
使用 C 驱动的 mongoc_bulk_operation_t 时,即使你在同一个逻辑会话(mongoc_client_start_session())里创建 bulk,并调用 mongoc_client_session_start_transaction(),也必须显式把 bulk 操作绑定到该 session —— 否则 bulk 仍走普通路径,绕过事务。
- 关键步骤:调用
mongoc_bulk_operation_set_session()把 session 传进去 - 漏掉这步,
mongoc_bulk_operation_execute()就是裸批量,不进事务 - 事务内 bulk 执行失败时,整个事务 abort,不会留下部分生效状态
真正容易被忽略的点:事务不是开关一开就万事大吉。它带来锁竞争、内存快照开销、超时限制,尤其在高并发批量场景下,单次事务里塞太多操作反而降低吞吐。设计前先问自己——这些文档真需要强一致性绑定?还是能接受最终一致?











