MongoDB 4.0 多文档事务仅支持单一分片或副本集内的 ACID,不支持跨分片事务;涉及多分片时直接报错,因未实现分布式两阶段提交;需确保所有操作落在同一分片且满足快照隔离与正确持久化配置。

MongoDB 4.0 的多文档事务不提供跨分片事务的 ACID 保证,仅在单一分片(或副本集)内满足 ACID —— 这是绝大多数线上误用的根源。
为什么 session.startTransaction() 在分片集群里会报错?
分片集群中,一个事务若涉及多个分片(例如查询路由到 shard1、更新落在 shard2),MongoDB 4.0 直接拒绝执行,抛出 TransactionNumbersMismatch 或 NoSuchTransaction 错误。这不是 bug,而是设计限制:4.0 未实现分布式两阶段提交(2PC)的协调器逻辑。
- 只有所有操作落在同一分片(即所有文档共享相同分片键值)时,事务才可成功启动
- 使用
sh.enableSharding("db")后必须显式设置分片键,且事务前需确保find/update的查询条件能命中唯一分片(如{userId: "u123"},且userId是分片键) - 聚合管道中的
$lookup若跨集合且集合未共置(collation或shard key不一致),也会导致事务失败
commitTransaction() 超时后到底发生了什么?
默认事务最大运行时间是 60 秒(由 transactionLifetimeLimitSeconds 控制),超时后服务器自动 abort,但客户端可能收不到明确响应 —— 此时状态不可知,不能假设“没提交就是没改”。
- 务必在调用
commitTransaction()后检查返回结果,而非只捕获异常;未收到确认 ≠ 事务未提交 - 服务端 abort 不会回滚已写入的 oplog 条目,而是靠后台周期性清理(依赖
oplog可见性窗口),因此极端情况下可能短暂出现“已提交又消失”的幻读 - 生产环境建议将超时设为 30 秒以内,并在业务层实现幂等重试(用
txnNumber+ 唯一业务 ID 去重)
副本集内事务的隔离级别其实是“快照隔离”(SI),不是串行化
MongoDB 不提供 SERIALIZABLE 隔离级别,事务基于 WiredTiger 的快照机制实现:每个事务启动时获取一个全局单调递增的 clusterTime,读取该时间点之前已提交的数据版本。
- 这意味着它无法防止“写偏斜(write skew)”:两个并发事务各自读取不同文档、各自判断条件成立、各自写入,最终违反全局约束(如账户总余额不变)
-
readConcern: "snapshot"是事务默认读关注,但普通读操作(非事务内)即使设了该值,也不受事务快照保护 - 没有
SELECT ... FOR UPDATE语义,无法主动加行锁;冲突只在commit阶段检测(通过txnNumber和lsid校验),失败时抛WriteConflict
真正容易被忽略的是:事务日志(oplog)条目在 commit 后才写入,而 WiredTiger 的存储引擎提交和 oplog 持久化不是原子的 —— 崩溃恢复时依赖 oplog 回放,所以必须确保 journal: true 且 writeConcern: {j: true},否则可能丢失已确认的事务。


















