一句话定义
两阶段提交(2PC,Two-Phase Commit)用「准备投票 + 全局决定」让跨节点事务原子提交,但存在协调者单点阻塞问题;柔性事务(TCC、SAGA、消息最终一致)则放弃强原子性,用补偿与重试换取可用性与吞吐。
为什么重要
- 分库分表、微服务拆库之后,一笔业务天然跨多个数据库实例,「跨库要么全成要么全败」必须靠协议而非运气。
- 2PC 是 XA 事务、NewSQL(Spanner、TiDB 的 Percolator 变体)的共同内核,不懂 2PC 就读不懂分布式数据库的任何权衡。
- 柔性事务是互联网业务的实际主流:理解补偿与幂等,比记住协议图更能救命。
前置知识
kp-017(本地事务与提交语义)、建议先了解 kp-023 的复制概念。
核心概念
- 协调者(Coordinator)/ 参与者(Participant)。
- 2PC 阶段一(Prepare):参与者写好 redo/undo 并落盘,回 Yes/No;阶段二(Commit):协调者收集全部 Yes 才下发 Commit,任一 No 或超时则 Abort。
- 不确定区间(In-doubt):参与者投完 Yes 等待决定期间,锁与资源必须保持。
- 3PC:增加 Pre-Commit 与超时自治,降低阻塞但引入脑裂窗口,工程罕见。
- TCC:Try(预留资源)/ Confirm(确认)/ Cancel(释放),业务层两阶段。
- SAGA:长事务拆为本地事务序列,每步配补偿动作,失败逆序补偿。
- 幂等(Idempotency):同一操作执行多次效果不变,是一切重试的前提。
原理与机制
2PC 的正确性来自「投票点」:一旦全部参与者表示 Prepare 成功,事务必定可提交(每个参与者已把 Undo/Redo 持久化,之后无论谁崩溃都能恢复);一旦有人投 No,必定可中止。代价有两个:其一是阻塞——协调者在阶段二前崩溃,参与者抱着锁进入 in-doubt 状态直到协调者恢复,这是 2PC 最著名的弱点;其二是延迟——至少两轮同步日志刷盘往返,把单机事务的毫秒级放大到跨机房百毫秒级。柔性事务把原子性降级为「最终一致」:SAGA 的每步是本地事务,失败时逆序执行补偿(订金已扣则退款),补偿本身必须幂等且可重试;消息最终一致用「本地消息表 + 投递重试」把跨服务写变成可靠的异步事件。三者的选择矩阵:强一致短事务用 2PC/XA;可补偿的业务流程用 SAGA;资源预留语义清晰(冻结额度)用 TCC。
实例或案例
跨「订单库」与「账户库」扣款下单。2PC 方案:应用作为协调者,两库 Prepare(锁住金额行与库存行)→ 全 Yes 则双双 Commit;协调者宕机时两库行锁悬挂,直到恢复。SAGA 方案:本地事务一「创建订单(状态待支付)」,发消息触发本地事务二「扣款」,扣款失败则执行补偿「订单置为已取消」——任一时刻系统可见状态不一致(订单存在但未扣款),但最终收敛;每条消息带唯一业务单号做幂等键,防止重试导致重复扣款。
常见误区
- 把 2PC 当「免费强一致」:它的可用性与延迟代价巨大,跨机房高并发场景几乎不可用。
- 补偿逻辑不幂等:重试场景下「补偿三次」可能多退三笔款,幂等键是底线。
- 用消息队列替代事务而不处理投递失败:本地消息表(先落库后投递)或事务消息是必须的桥梁。
自测题
- 2PC 中参与者投出 Yes 后协调者失联,参与者处于什么状态? 答:in-doubt(不确定)状态:既不能提交也不能回滚,必须持有锁等待协调者恢复后读取决定。
- SAGA 为什么要求每步可补偿且幂等? 答:补偿是失败路径的逆序回放,可能被重试多次;不可补偿的步骤会留下无法回退的半成品状态。
公式或模型
2PC 提交延迟 ≈ 2 × RTT + 2 × fsync(两轮消息 + 两轮刷盘);跨机房 RTT 5ms、fsync 1ms 时约 12ms 起步,叠加锁等待后远高于单机事务。
图示
直观类比
2PC 像表决重要动议:主席先问所有人「想好了吗」(Prepare,想好即锁死自己的答案),全员「想好」才宣布通过(Commit),只要有一个人没想好就整体作废——主席中途离场,全员只能举着手干等。SAGA 则像装修队:每个工序先干完,出问题就按反序「拆掉重来」。
与其他知识点的关系
kp-014 的 ACID 是它的单机版本;kp-026 的 Raft 解决的正是 2PC 协调者单点问题(复制协调者本身);kp-024 的分片策略决定哪些事务被迫跨节点。
延伸阅读
- Gray《Notes on Data Base Operating Systems》(1978,2PC 奠基文献)
- 《数据密集型应用系统设计》(Martin Kleppmann)第 9 章事务