分片集群事务必须走两阶段提交(2PC),因为跨shard时无法靠单节点WAL或oplog保证原子性;mongos作为协调者需先预提交再统一提交,引入额外网络往返、config server写入及最慢shard等待等延迟。

分片集群事务为什么必须走两阶段提交(2PC)
因为事务涉及多个 shard 时,MongoDB 无法靠单节点的 WAL 或 oplog 保证原子性——每个 shard 是独立副本集,各自维护数据和日志。mongos 必须作为协调者,先让所有参与 shard 预提交(prepare),再统一决定 commit 或 abort。这个 prepare 阶段会写入 config.transactions 表并广播状态,而 commit 阶段要等全部 shard 确认才返回成功。整个链路多出至少 2 次网络往返 + 1 次 config server 写入,这是副本集事务完全不存在的开销。
2PC 在分片集群中放大哪些具体延迟
实测表明,一个跨 2 个 shard 的简单 update 事务,相比等价单 shard 事务,平均增加 120–350ms 延迟,主要来自:
-
prepareTransaction请求在 mongos 和各 shard 之间至少 1 轮 RTT(通常 >40ms,跨机房更甚) - 所有 shard 必须向 config server 写入 prepare 状态,config server 成为单点瓶颈(尤其高并发时)
- commit 阶段需等待最慢 shard 的响应,任一 shard GC、磁盘抖动或网络丢包都会拖慢全局
- 事务快照需跨 shard 对齐逻辑时间(
clusterTime),mongos 要做额外时钟协调
副本集事务不走 2PC,但仍有隐藏成本
副本集内事务虽免于 2PC,但强制 writeConcern: "majority",且每个操作都要维护快照版本、检测冲突、写入 oplog 和 transaction table。这导致:
- 写入吞吐下降 8–10 倍(实测 1000 文档事务 vs 单文档批量)
- 失败率从 0.2% 升至 15%(当事务内更新超 50 文档)
-
globalLock持锁时间变长,阻塞其他读写请求 - 即使只改 3 个文档,只要跨集合(如 users + logs),仍触发完整事务路径
怎么快速判断当前慢事务卡在哪一环
别猜,用 db.currentOp() 直接看现场:
- 若
secs_running高 +waitingForLock: false→ 真实执行慢(查索引、聚合重) - 若
waitingForLock: true→ 锁被其他事务/操作占用(查currentOp中secs_running最大的那个) - 若
secs_running低但客户端超时 → 大概率卡在 2PC 的 prepare 或 commit 等待阶段(此时currentOp可能不显示,需结合 mongos 日志 grep"prepareTransaction") - 用
db.setProfilingLevel(1, { slowms: 50 })捕获事务内操作,按lsid+txnNumber聚合,看哪一步耗时突增
真正难定位的是 prepare 成功但 commit 卡住的情况——它不进 Profiler,也不在 currentOp 显式暴露,只能靠 mongos 日志里的 "waiting for majority commit" 关键字反推。











