MongoDB 4.0+跨集合事务能保证原子性,但必须满足:部署为副本集或分片集群(非standalone)、所有集合同库、每个操作显式传入session、且startTransaction时显式配置readConcern:"snapshot"和writeConcern:{w:"majority"}。

MongoDB 4.0+ 的跨集合事务能真正保证原子性,但前提是你的部署和写法都踩对关键点——错一个,session.commitTransaction() 就会直接失败或 silently 不生效。
确认你用的是副本集或分片集群,不是 standalone
单节点 MongoDB 实例调用 session.startTransaction() 会立刻报错:CommandNotSupportedOnStandalone。这不是配置问题,是底层硬限制:事务依赖 oplog 和 WiredTiger 的 timestamp 机制,这两者在 standalone 模式下不可用。
- 运行
rs.status()—— 如果返回"ok" : 1且有多个成员,说明是副本集 - 运行
sh.status()—— 如果不报错且显示分片信息,说明是分片集群 - 如果两个命令都报
not master或no replset config,那你当前就是 standalone,必须先升级为至少 3 节点副本集(或 1 主 + 2 arbiter)
所有操作必须在同一数据库内,且显式传入 session 参数
事务不允许跨数据库,哪怕只是 db.orders 和 db.logs 这样逻辑上同属一个业务的集合也不行。错误提示是 InvalidNamespace,不是运行时异常,而是命令被服务端直接拒绝。
- 把相关集合统一放在一个库下,比如全部移到
bank数据库:accounts、transactions、audit_logs - 每个 CRUD 操作必须带
{ session }选项,漏掉任意一个,该操作就游离在事务外,无法回滚 - 错误写法:
db.accounts.updateOne(...)→ 缺少session,它会立即提交,破坏原子性 - 正确写法:
db.accounts.updateOne({...}, {...}, { session })
读关注和写关注必须显式设置,否则快照隔离失效
即使事务成功提交,没配 readConcern: "snapshot" 和 writeConcern: { w: "majority" },你也拿不到 ACID 里“一致性”和“隔离性”的保障。默认的 local 读关注可能读到未提交数据,w: 1 写关注则可能在主节点宕机后丢失已 commit 的事务。
- 启动事务时要指定选项:
session.startTransaction({ readConcern: { level: "snapshot" }, writeConcern: { w: "majority" } }) - Node.js 驱动中,
startTransaction()的参数对象必须完整传递,不能只传空对象 - Python 的
with client.start_session() as s:后,s.start_transaction()不带参数 = 默认local+w: 1,等同于没开事务语义
别在事务里做 HTTP 请求、日志打印或等待用户输入
事务持有时间越长,锁住的文档越多,其他请求排队越久。MongoDB 默认事务超时是 60 秒(transactionLifetimeLimitSeconds),超时自动 abort,但更危险的是人为阻塞:
- 在
try块里加console.log()然后断点调试?事务卡住,所有相关文档被锁死 - 调用外部支付网关回调再继续?网络延迟一高,事务直接超时,但外部服务可能已执行成功,造成状态不一致
- 写入前先查一遍余额再判断?没问题;但查完后不做原子更新(比如用
$inc而非两步读-改-写),仍可能被并发覆盖
最容易被忽略的一点:事务不是魔法盒。它只包裹 MongoDB 内部操作,对外部世界完全无感知。任何跨边界的交互,都得靠应用层自己兜底,比如幂等 key、补偿任务或状态机驱动的最终一致性。











