Seata 的 AT 模式和 TCC 模式有什么区别?
Seata 是一个开源的分布式事务解决方案,提供了四种事务模式:AT、TCC、Saga 和 XA。其中,AT 模式和 TCC 模式是日常开发中最常用的两种。
它们虽然都属于两阶段提交(2PC)的演进,但核心设计思想、实现机制和适用场景有很大区别。简单来说:AT 模式是框架自动完成的数据库级代理,而 TCC 模式是需要开发者手工编写代码的业务级补偿。
以下是两者的详细区别:
1. 核心机制对比
AT 模式(Automatic Transaction)—— 自动化、无侵入
AT 模式是 Seata 创新的一种非侵入式分布式事务解决方案。它依赖于支持本地 ACID 事务的关系型数据库。
- 第一阶段(执行并提交): Seata 的 JDBC 代理会拦截业务 SQL,解析 SQL 语义,找到要更新的业务数据,保存数据修改前的状态(before image)和修改后的状态(after image)到
undo_log表中。然后直接提交本地事务,释放本地数据库锁。 - 第二阶段(决议):
- 如果全局提交: 异步清理
undo_log中的记录,非常轻量快速。 - 如果全局回滚: 读取
undo_log中的before image,生成反向 SQL(例如把 UPDATE 改回原来的值,INSERT 改为 DELETE)并执行,完成数据恢复。
- 如果全局提交: 异步清理
TCC 模式(Try-Confirm-Cancel)—— 手工化、强侵入
TCC 是一种业务层面的两阶段提交,完全不依赖数据库底层的机制,而是需要开发者针对每个操作编写三个方法:
- 第一阶段 - Try(尝试): 做业务检查(一致性)和资源预留(隔离)。例如:买东西时不直接扣库存,而是把这部分库存“冻结”起来。
- 第二阶段 - Confirm(确认): 确认执行业务操作。如果 Try 成功,则执行 Confirm,真正扣除冻结的库存。Confirm 操作要求具备幂等性。
- 第二阶段 - Cancel(取消): 取消执行业务操作。如果全局事务回滚,则执行 Cancel,释放 Try 阶段冻结的库存。Cancel 操作也要求具备幂等性。
2. 多维度差异总结对比表
| 维度 | AT 模式 | TCC 模式 |
|---|---|---|
| 业务侵入性 | 极低。开发者只需写普通 SQL 并加上 @GlobalTransactional 注解,无需改动业务逻辑。 |
极高。一个接口需要拆分成 Try、Confirm、Cancel 三个方法。 |
| 底层依赖 | 强依赖支持 ACID 的关系型数据库(MySQL、Oracle 等),依赖 Seata 的 SQL 解析器。 | 无底层依赖。支持关系型数据库、NoSQL(Redis/MongoDB)、甚至是调用第三方 RPC 接口。 |
| 锁与隔离性 | 依赖 Seata 的全局锁机制来保证读写隔离,可能会存在锁竞争导致的性能下降。 | 无全局锁。隔离性由业务代码自己实现(即 Try 阶段的“资源预留”),锁的粒度更细。 |
| 性能 | 一般。解析 SQL、生成并写入 undo_log、以及全局锁竞争会带来一定的性能损耗。 |
较高。直接执行业务逻辑,没有 SQL 解析和额外的日志写入,且不需要加全局锁。 |
| 开发难度 | 简单,几乎和单机事务开发体验一致。 | 困难。需要处理幂等性、空回滚、防悬挂三大难题。 |
3. TCC 模式开发必须解决的三大问题
如果选择 TCC 模式,开发者在编写 Confirm 和 Cancel 方法时,必须在代码层面解决以下问题(Seata 新版本提供了一些防悬挂拦截器,但业务逻辑仍需严谨):
- 幂等性: 网络超时可能导致 Confirm 或 Cancel 被多次重试调用,代码必须保证执行一次和执行多次的结果是一样的。
- 空回滚: 当 Try 阶段因为网络拥堵根本没有执行,或者发生异常失败了,全局事务触发回滚,直接调用了 Cancel。此时 Cancel 方法需要判断出 Try 没有执行过,从而直接返回成功,不能去“释放”根本没冻结的资源。
- 防悬挂: 极端的网络情况下,Cancel 比 Try 先执行了。此时 Cancel(空回滚)执行完毕后,Try 请求才到达并执行,这会导致资源被永远冻结(悬挂)。必须记录事务状态,拒绝在 Cancel 执行后到达的 Try 请求。
4. 如何选择?(适用场景)
优先考虑使用 AT 模式:
- 绝大部分日常的增删改查(CRUD)业务。
- 项目使用的是主流关系型数据库(如 MySQL)。
- 要求快速迭代、降低开发成本,团队不想为了事务编写大量的补偿代码。
- 系统的并发量没有达到极其变态的程度(能容忍一定的性能损耗)。
以下情况必须使用/建议使用 TCC 模式:
- 核心高并发链路: 例如电商的扣库存、金融系统的扣款,AT 模式的全局锁可能导致数据库行锁竞争严重,TCC 性能更好。
- 非关系型数据库: 业务数据存储在 Redis、MongoDB 等不支持 SQL 解析的 NoSQL 中。
- 跨公司/跨系统的接口调用: 比如调用支付宝扣款、调用第三方发短信,这些接口你无法控制它们的数据库,只能通过业务逻辑去调用它们的“退款”或“撤销”接口(Cancel)。