2PC / XA——强一致分布式事务的基础模型
- 先从问题开始:为什么不能直接提交?
- 2PC 是什么?
- 第一阶段:Prepare
- Prepare 到底意味着什么?
- 第二阶段:Commit
- 如果有人 Prepare 失败呢?
- 完整流程
- 为什么一定需要两阶段?
- 2PC 最大的问题:锁资源
- 举个高并发场景
- 协调者挂了怎么办?
- 第二阶段消息丢了也会麻烦
- 2PC 的核心缺点
- 那 2PC 有没有优点?
- XA 和 2PC 是什么关系?
- XA 的基本思想
- MySQL 里的 XA
- Seata XA 又是什么?
- Seata XA 的特点
- 2PC 和本地事务有什么本质区别?
- 为什么 2PC 不适合长事务?
- 2PC 和 TCC 有什么直觉区别?
- 面试题:
-
- 2PC 为什么是阻塞协议?
- 2PC 能绝对保证一致性吗?
- XA 和 2PC 有什么区别?
- 把整个知识链串起来
- 本篇博文必须记住的 8 个结论
上一篇:《ACID、CAP、BASE,以及强一致性和最终一致性》 下一篇:《TCC——业务层面的分布式事务》
本篇开始进入 2PC / XA——强一致分布式事务的基础模型。
需要搞懂的不是“背两阶段流程”,而是:
为什么需要两个阶段、Prepare 到底干了什么、为什么会锁资源、协调者挂了会怎样,以及 XA 和 2PC 的关系。
先从问题开始:为什么不能直接提交?
假设一个转账业务涉及两个数据库:
DB1:A 账户 -100
DB2:B 账户 +100
最直接的做法:
先提交 DB1
再提交 DB2
问题来了:
DB1 COMMIT 成功 ✅
DB2 COMMIT 失败 ❌
结果:
A -100
B 没 +100
业务不一致。
所以我们希望:
在真正提交之前,先确认所有参与者都“准备好了”。
这就是 2PC 的核心思想。
2PC 是什么?
2PC,全称:
Two-Phase Commit
中文:
两阶段提交。
参与角色主要有两个:
Coordinator
协调者
Participant
参与者
例如:
Coordinator
│
┌──────────┴──────────┐
↓ ↓
Participant A Participant B
DB1 DB2
协调者负责:
询问
决定
通知
参与者负责:
执行本地事务
准备提交
最终提交 / 回滚
第一阶段:Prepare
第一阶段不是正式提交。
而是:
先执行事务,但暂时不要最终提交。
例如:
Coordinator
│
├── Prepare → DB1
└── Prepare → DB2
DB1:
A账户 -100
DB2:
B账户 +100
但此时:
还没有最终 COMMIT
参与者会告诉协调者:
DB1:我准备好了
DB2:我也准备好了
也就是:
YES
YES
Prepare 到底意味着什么?
这是本篇博文最关键的点。
Prepare 不是简单说一句:
“我可以”
而是通常意味着:
我已经把事务执行到一个“只差最后提交”的状态。
也就是说:
业务 SQL 已经执行
日志已经持久化
需要的锁还持有着
例如:
UPDATE account
SET balance = balance – 100
WHERE id = 1;
这条 SQL 已经执行。
但:
事务还没有最终结束
所以资源通常仍然被锁住。
第二阶段:Commit
如果所有参与者都回答:
YES
协调者决定:
全局提交
然后:
Coordinator
│
├── Commit → DB1
└── Commit → DB2
最终:
DB1 COMMIT
DB2 COMMIT
事务完成。
如果有人 Prepare 失败呢?
例如:
DB1:YES
DB2:NO
可能 DB2 因为:
余额不足
SQL 执行失败
死锁
数据库异常
无法进入 Prepared 状态。
协调者就会通知:
ROLLBACK
于是:
Coordinator
│
├── Rollback → DB1
└── Rollback → DB2
最终:
大家都不提交
所以 2PC 追求的是:
要么一起 Commit
要么一起 Rollback
完整流程
正常提交: 
失败:

为什么一定需要两阶段?
因为协调者必须先回答一个问题:
所有人都具备提交条件了吗?
如果不做 Prepare:
DB1 COMMIT
↓
DB2 COMMIT
DB1 一旦提交,就不能轻易撤销。
而 Prepare 的价值就是:
先不要真正提交
↓
所有人先进入“可提交状态”
↓
再统一决定
所以你可以把 2PC 理解成:
第一阶段:大家都准备好了吗?
第二阶段:既然都准备好了,那一起提交
2PC 最大的问题:锁资源
现在来看它为什么性能不好。
假设 DB1 执行:
UPDATE account
SET balance = balance – 100
WHERE id = 1;
在 Prepare 后:
事务不能结束
因为它还在等待协调者最终决定。
于是可能一直持有:
行锁
表锁
事务资源
连接
这时候另一个请求也想修改这个账户:
Transaction B
它可能只能:
等待……
所以:
Prepare
↓
等待协调者
↓
资源持续被占用
↓
并发下降
这就是 2PC 很大的代价。
举个高并发场景
某热门商品库存:
stock = 100
大量用户同时抢购。
如果每一个请求都进入分布式 2PC:
锁库存
↓
Prepare
↓
等待其他服务
↓
支付 Prepare
↓
订单 Prepare
↓
统一 Commit
库存锁可能持有很长时间。
结果:
吞吐量下降
请求等待
超时增加
锁竞争严重
所以:
2PC 的一致性很强,但协调成本和锁资源成本也很高。
协调者挂了怎么办?
这是经典问题。
假设:
DB1 已 Prepare
DB2 已 Prepare
它们都在等待:
Commit?
还是 Rollback?
就在这时:
Coordinator 崩了
参与者不知道最终决定。
于是:
DB1:我到底提交不提交?
DB2:我也不知道。
这就是 2PC 中经典的:
阻塞问题。
参与者可能必须继续保持 Prepared 状态。
也意味着:
锁不能释放
资源不能释放
一直等协调者恢复。
第二阶段消息丢了也会麻烦
假设:
Coordinator 决定 Commit
发送:
Commit → DB1
Commit → DB2
结果:
DB1 收到 ✅
DB2 没收到 ❌
那么:
DB1 已提交
DB2 仍在 Prepared
DB2 不知道:
到底该提交还是回滚
最终往往需要:
恢复
重试
状态查询
日志判断
所以分布式事务不是一句:
“加个协调器”
就解决了。
2PC 的核心缺点
你可以直接记成 4 个:
1. 同步阻塞
2. 资源锁定时间长
3. 协调者故障影响大
4. 网络异常会产生不确定状态
再加一个工程层面:
5. 性能和吞吐通常较差
那 2PC 有没有优点?
当然有。
最大优点:
一致性强
特别适合:
对一致性要求非常高
参与资源比较有限
并发压力不算特别高
事务时间较短
的场景。
例如:
传统金融核心交易
企业内部系统
强一致数据同步
但不能简单理解成:
金融 = 一定 2PC
实际金融系统也大量使用:
账务
消息
补偿
对账
幂等
共同保证业务一致性。
XA 和 2PC 是什么关系?
这是面试高频问题。
先记一句:
2PC 是一种协议思想,XA 是一种标准接口规范。
2PC 讲的是:
Prepare
Commit / Rollback
而 XA 解决的是:
不同事务资源怎么按照统一接口参与这种分布式事务?
XA 最早由 X/Open 提出。
一个典型 XA 架构:
Application
│
↓
Transaction Manager
│
├── XA Resource → MySQL
└── XA Resource → Oracle
其中:
TM:Transaction Manager
事务管理器
RM:Resource Manager
资源管理器
数据库可以作为 RM。
XA 的基本思想
事务管理器:
TM
负责控制全局事务。
多个数据库:
RM1
RM2
RM3
分别加入同一个全局事务。
然后 TM 使用 XA 接口:
XA START
XA END
XA PREPARE
XA COMMIT
XA ROLLBACK
控制参与者。
因此:
XA
↓
标准化接口
2PC
↓
核心提交协议
你可以理解为:
XA 是 2PC 在数据库事务资源上的一种标准化实现方式。
MySQL 里的 XA
MySQL 支持 XA 事务。
概念上可以这样理解:
XA START 'xid';
UPDATE account
SET balance = balance – 100
WHERE id = 1;
XA END 'xid';
XA PREPARE 'xid';
这时事务进入:
Prepared
最后由事务管理器决定:
XA COMMIT 'xid';
或者:
XA ROLLBACK 'xid';
实际开发中一般不会手动这样写,而是由:
事务管理器
框架
中间件
帮你处理。
Seata XA 又是什么?
Seata 里也有 XA 模式。
可以粗略理解成:
应用
│
↓
Seata TC
│
├── MySQL XA
├── MySQL XA
└── MySQL XA
其中:
TC = Transaction Coordinator
TM = Transaction Manager
RM = Resource Manager
Seata TC 负责协调全局事务。
各数据库使用 XA 能力参与。
Seata XA 的特点
优点:
强一致
利用数据库原生 XA 能力
业务侵入相对较低
缺点:
Prepare 阶段会锁资源
性能受影响
依赖数据库 XA 支持
长事务不适合
所以:
Seata XA
并没有从根本上消除 2PC 的特点。
它只是:
帮你更方便地管理和协调 XA 两阶段事务。
2PC 和本地事务有什么本质区别?
本地事务:
一个事务管理器
↓
一个数据库资源
2PC:
一个全局协调者
↓
多个事务资源
本地事务:
BEGIN
SQL
COMMIT
2PC:
多个数据库分别执行
↓
全部 Prepare
↓
全局统一 Commit / Rollback
为什么 2PC 不适合长事务?
假设业务:
用户下单
↓
酒店预订
↓
机票预订
↓
租车
↓
保险
↓
支付
整个流程可能几十秒,甚至几分钟。
如果都使用 2PC:
酒店数据库锁着
机票数据库锁着
租车数据库锁着
支付数据库锁着
等待:
所有业务完成
这个代价非常高。
所以长事务通常更适合:
Saga
后面会专门讲解 Saga。
2PC 和 TCC 有什么直觉区别?
现在先建立一个直觉。
2PC:
数据库层面
Prepare
Commit
Rollback
TCC:
业务层面
Try
Confirm
Cancel
比如扣款。
2PC:
数据库 UPDATE
↓
事务锁住
↓
等待 Commit
TCC:
Try:
把 100 元变成“冻结状态”
Confirm:
正式扣除
Cancel:
释放冻结
TCC 不一定长时间持有数据库锁。
所以 TCC 用:
业务资源冻结
换取:
数据库锁持有时间更短
代价是:
业务代码复杂很多
这就是为什么后面会出现 TCC。
面试题:
2PC 为什么是阻塞协议?
答题思路:
在第一阶段,参与者完成本地事务并进入 Prepared 状态后,需要等待协调者的第二阶段决策。在等待期间通常需要持有事务资源和锁,因此如果协调者迟迟没有发送 Commit 或 Rollback,参与者就无法独立结束事务,会产生阻塞。
这句话基本就是标准回答。
2PC 能绝对保证一致性吗?
不要简单回答:
能
更好的回答是:
2PC 设计目标是保证原子提交,但在真实分布式系统中仍需要处理协调者故障、消息丢失、网络分区、节点恢复等异常,因此工程上依赖日志、恢复机制、重试和状态查询来维持事务一致性。
这样更准确。
XA 和 2PC 有什么区别?
可以这样回答:
2PC 是一种分布式原子提交协议,定义了 Prepare 和 Commit/Rollback 两个阶段;XA 是 X/Open 定义的分布式事务接口规范,用于让事务管理器统一协调不同资源管理器。XA 通常通过两阶段提交完成全局事务。
这是非常常见的一道题。
把整个知识链串起来
现在我们的知识已经变成:
本地事务
↓
ACID
↓
跨数据库
↓
出现一致性问题
↓
需要协调
↓
2PC
↓
Phase 1:Prepare
↓
Phase 2:Commit / Rollback
↓
获得较强一致性
↓
代价:
锁资源
阻塞
性能下降
协调者故障
↓
于是继续出现
TCC / Saga / MQ 最终一致性
你会发现:
后面的分布式事务方案,本质上很多都是在解决 2PC 太“重”的问题。
本篇博文必须记住的 8 个结论
下一篇博文将直接进入 TCC。
因为你现在已经知道 2PC 最大的问题:
Prepare 后资源一直锁着
那么 TCC 要解决的问题就是:
能不能不用长时间锁数据库资源,而是由业务自己先“预留资源”?
下一篇我会重点讲:
Try 到底做什么
Confirm 怎么设计
Cancel 怎么设计
为什么必须幂等
什么是空回滚
什么是悬挂
TCC 怎么设计账户扣款
TCC 为什么比 2PC 性能更高
TCC 又为什么开发成本更高
上一篇:《ACID、CAP、BASE,以及强一致性和最终一致性》 下一篇:《TCC——业务层面的分布式事务》
若有转载,请标明出处:https://blog.csdn.net/CharlesYuangc/article/details/165342002
网硕互联帮助中心


评论前必须登录!
注册