2PC、TCC、SAGA、本地消息表的原理和优缺点分别是什么?在实际项目中如何选择合适的分布式事务方案?
| 特性 | 2PC | TCC | SAGA | 本地消息表 |
|---|---|---|---|---|
| 一致性 | 强一致性(ACID) | 强一致性(近似 ACID) | 最终一致性 | 最终一致性 |
| 业务侵入性 | 低(依赖数据库) | 高(需实现 3 个方法) | 中(需实现补偿事务) | 低(需加消息表) |
| 性能 | 低(同步阻塞) | 高(无阻塞) | 高(无阻塞) | 高(异步消息) |
| 实现复杂度 | 低 | 高(处理幂等 / 空回滚) | 中(协调式易管理) | 低 |
| 适用事务长度 | 短事务 | 短事务 | 长事务 | 中短事务 |
| 核心问题 | 同步阻塞、单点故障 | 业务侵入、实现复杂 | 补偿逻辑、最终一致 | 消息延迟、幂等性 |
SAGA
将分布式事务拆分为一系列本地事务的有序执行链,每个本地事务都有对应的补偿事务:
正向执行
按顺序执行每个本地事务 T1, T2, …, Tn。
如果所有本地事务都执行成功,事务提交。
反向补偿
如果某个本地事务 Ti 执行失败,按逆序执行补偿事务 Ci-1, Ci-2, …, C1。
补偿事务的作用是撤销对应正向本地事务的执行结果,恢复到事务执行前的状态。
TCC(Try-Confirm-Cancel,补偿事务)

TCC 是一种业务侵入式的分布式事务方案,将每个业务操作拆分为三个独立的方法,由业务代码实现:
1.Try 阶段
尝试执行业务操作,预留资源(如冻结资金、锁定库存),保证操作的幂等性。
此阶段只做资源检查和预留,不执行最终业务逻辑。
2.Confirm 阶段
确认执行业务操作,使用预留的资源完成最终逻辑。
只有当所有参与者的 Try 阶段都成功时,才会执行 Confirm 阶段。
3.Cancel 阶段
取消执行业务操作,释放预留的资源。
如果任何一个参与者的 Try 阶段失败,会执行所有参与者的 Cancel 阶段。
幂等、空回滚、悬挂?
幂等性就是无论接口调用多少次,返回的结果应该具有一致性。
空回滚(try没执行就cancel):当某分支事务的try阶段阻塞时,可能导致全局事务超时而触发二阶段的cancel操作。在未执行try操作时先执行了cancel操作,这时cancel不能做回滚,就是空回滚。
业务悬挂(cancel之后try):对于已经空回滚的业务,如果以后继续执行try,就永远不可能confirm或cancel
- 资金业务的具体例子:
为了实现空回滚、防止业务悬挂,记录冻结金额的同时,记录当前事务id和执行状态1
2
3
4
5
6
7CREATE TABLE `account_freeze_tbl` (
`xid` varchar(128) NOT NULL COMMENT '事务id',
`user_id` varchar(255) DEFAULT NULL COMMENT '用户id',
`freeze_money` int(11) unsigned DEFAULT '0' COMMENT '冻结金额',
`state` int(1) DEFAULT NULL COMMENT '事务状态,0:try,1:confirm,2:cancel',
PRIMARY KEY (`xid`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;
1.Try业务
记录冻结金额和事务状态0到account_freeze表
扣减account表可用金额
2.Confirm业务
根据xid删除account_freeze表的冻结记录(因为如果一个事务confirm那么记录就没有意义了)
3.Cancel业务
修改account_freeze表,冻结金额为0,state为2
修改account表,恢复可用金额
- 如何判断是否空回滚:
cancel业务中,根据xid查询account_freeze,如果为null则说明try还没做,需要空回滚
- 如何避免业务悬挂:
try业务中,根据xid查询account_freeze ,如果存在金额=0/state=2则证明Cancel已经执行,拒绝执行try业务
TCC vs 2PC
TCC只需要处理业务异常情况,异常处理相对简单。 2PC适用于对事务一致性要求较高的场景,例如银行转账等,需要保证数据致性和完整性。 而TCC适用于对事务一致性要求不那么高的场景,例如电商库存扣减等,需要保证数据最终一致性即可。
本地消息表
写本地消息+状态,然后链式流转。