news 2026/9/29 21:32:49

分布式事务:Seata AT/TCC/SAGA 的取舍与踩坑

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
分布式事务:Seata AT/TCC/SAGA 的取舍与踩坑

分布式事务:Seata AT/TCC/SAGA 的取舍与踩坑

关键词标签:Seata、分布式事务、AT 模式、TCC、Saga、全局锁、回滚补偿

一个容易被忽略的事实:Seata 的 AT 模式之所以能"无侵入"地回滚业务 SQL,靠的不是数据库事务,而是在业务库中额外维护一张undo_log表记录前后镜像,再加一把由 TC 维护的全局锁。这意味着——如果你的业务表没有主键、SQL 形态超出 Seata 解析器覆盖范围、或者把数据写进了information_schema这类系统表,AT 模式很可能在第一阶段就无法正确生成回滚信息。这不是 bug,是 AT 的设计边界。

本系列前面讲过线程池、AQS、CompletableFuture 这些"单机并发"的底座,也讲过 Spring Boot 自动配置的加载链路。这一篇我们把视角从单机拉到分布式,专门聊 Seata 三种模式(AT / TCC / SAGA)的取舍逻辑和边界条件。下文中的order-service、storage-service仅为讲解示例(非真实系统)。


一、先想清楚:分布式事务到底在解决什么

分布式事务的本质矛盾只有一个:多个独立资源(DB、MQ、RPC 服务)的本地事务无法用一个 XA 事务串起来,或者串起来代价太高。

Seata 的定位不是替代 XA,而是提供一套"最终一致 + 自动补偿"的框架。它把一次全局事务拆成:

  • TC (Transaction Coordinator):独立部署的协调者,维护全局事务和分支事务状态;
  • TM (Transaction Manager):发起全局事务的一方,通常是业务入口;
  • RM (Resource Manager):管理分支事务的资源,通常是每个微服务里的数据源代理。

核心流程是经典的两阶段:第一阶段各分支各自提交本地事务并注册分支;第二阶段 TC 根据全局状态决定提交或回滚。三种模式的差别,全在"第一阶段做了什么"和"第二阶段怎么补偿"。


二、AT 模式:无侵入的代价是全局锁

2.1 它到底做了什么

AT(Auto Transaction)的核心机制可以用一句话概括:拦截业务 SQL,解析出 before image 和 after image,写入undo_log,然后提交本地事务。

关键点是:第一阶段本地事务就已经提交了。这就是为什么 AT 通常比 XA 性能更好——它没有把数据库连接一直挂到第二阶段。

那么回滚怎么办?靠undo_log里的前后镜像反向生成补偿 SQL。下面是 Seata 官方脚本mysql.sql中的undo_log表结构:

CREATE TABLE `undo_log` ( `branch_id` BIGINT NOT NULL COMMENT 'branch transaction id', `xid` VARCHAR(128) NOT NULL COMMENT 'global transaction id', `context` VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization', `rollback_info` LONGBLOB NOT NULL COMMENT 'rollback info', `log_status` INT(11) NOT NULL COMMENT '0:normal status,1:defense status', `log_created` DATETIME(6) NOT NULL COMMENT 'create datetime', `log_modified` DATETIME(6) NOT NULL COMMENT 'modify datetime', UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`) ) ENGINE = InnoDB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8mb4;

回滚时,Seata 会校验当前数据的 after image 是否仍等于undo_log里记录的 after image。如果不等,说明数据在全局事务提交后被别的事务改过,此时会抛出SQLUndoFailedException(其内部封装了RollbackRetryTimeoutException等场景),由 TC 决定重试或进入人工处理。这个校验逻辑是为了防止"脏回滚"覆盖别人的更新。

2.2 全局锁:AT 真正的命门

第一阶段提交本地事务前,Seata 会向 TC 申请全局锁(TC 侧维护lock_table)。申请成功才提交,否则重试。这是 AT 保证隔离性的核心。

// 示意:AT 模式下一个典型的业务方法(order-service 假设示例) @GlobalTransactional(name = "create-order", rollbackFor = Exception.class) public void createOrder(OrderDTO dto) { // 分支 1:本地订单库 orderMapper.insert(buildOrder(dto)); // 分支 2:远程扣库存(storage-service 也是 Seata RM) storageClient.deduct(dto.getSkuId(), dto.getCount()); // 分支 3:远程扣余额 accountClient.debit(dto.getUserId(), dto.getAmount()); }

这段代码看起来和普通@Transactional没区别,但@GlobalTransactional会通过GlobalTransactionalInterceptor开启全局事务,把 XID 通过 RPC 上下文透传到下游。

2.3 常见误区

  • 误区一:AT 能保证强一致。不能。它保证的是最终一致,且默认隔离级别是读未提交(因为第一阶段就提交了)。如果你在全局事务未结束时读到了中间态数据,是正常的。
  • 误区二:AT 支持所有 SQL。不支持。多表关联更新、INSERT ... SELECT、部分复杂子查询、DDL 语句,Seata 的 SQL 解析器可能解析失败。解析失败时,该分支不会生成undo_log,回滚就无从谈起。
  • 误区三:全局锁不占数据库锁。全局锁是 TC 内存 +lock_table表维护的,和数据库行锁是两码事。但它会造成跨服务的串行等待——两个全局事务改同一行数据,后到的那个会一直重试直到超时。

三、TCC 模式:把补偿逻辑交给业务

3.1 三段式

TCC(Try-Confirm-Cancel)把每个分支拆成三个方法:

  • Try:预留资源(冻结库存、预扣余额);
  • Confirm:确认使用预留资源,必须幂等;
  • Cancel:释放预留资源,必须幂等且能空回滚。
@LocalTCC public interface StorageTccAction { @TwoPhaseBusinessAction(name = "storageTcc", commitMethod = "confirm", rollbackMethod = "cancel") boolean tryDeduct(BusinessActionContext ctx, @BusinessActionContextParameter(paramName = "skuId") Long skuId, @BusinessActionContextParameter(paramName = "count") Integer count); boolean confirm(BusinessActionContext ctx); boolean cancel(BusinessActionContext ctx); }

@LocalTCC表示这是本地 TCC,BusinessActionContext会携带 XID 和分支 ID,用于幂等判断。

3.2 TCC 的三个经典坑

坑一:空回滚。Try 还没执行(比如网络超时,TC 以为失败了),Cancel 先到了。此时不能报错,要识别"没有对应的 Try 记录"直接返回成功。做法通常是 Cancel 时先查预留记录,查不到就返回 true。

坑二:幂等。网络抖动导致 Confirm/Cancel 被重复调用。必须靠唯一键(XID + branchId)去重。

坑三:悬挂。Cancel 比 Try 先到,Cancel 空回滚返回成功;随后 Try 才到达并真的扣了库存。此时全局事务已结束,库存永远扣着。解决办法是Try 执行前先检查是否已有对应的 Cancel 记录,有则拒绝执行。

这三个坑不是理论,是 TCC 落地的必备防御。任何声称"TCC 很简单"的说法,都忽略了这三个状态机。


四、SAGA 模式:长事务的最终答案

SAGA 的思路和 TCC 不同:它没有 Try,每个正向操作直接提交,失败时按逆序执行补偿操作。

Seata 的 SAGA 基于状态机(StateMachine),用 JSON 定义流程:

{ "Name": "orderSaga", "StartState": "CreateOrder", "States": { "CreateOrder": { "Type": "ServiceTask", "ServiceName": "orderService", "ServiceMethod": "create", "CompensateState": "CancelOrder", "Next": "DeductStorage" }, "DeductStorage": { "Type": "ServiceTask", "ServiceName": "storageService", "ServiceMethod": "deduct", "CompensateState": "RestoreStorage", "Next": "Succeed" }, "CancelOrder": { "Type": "CompensateState" }, "RestoreStorage": { "Type": "CompensateState" }, "Succeed": { "Type": "Succeed" } } }

SAGA 适合长流程、跨多个服务、无法长时间持有资源的场景,比如订单履约、跨系统对账。代价是:没有隔离性,中间状态对外可见,补偿逻辑必须由业务自己保证正确。


五、三种模式怎么选:一张决策表

维度ATTCCSAGA
侵入性低(加注解)高(写三个方法)中(写状态机 + 补偿)
隔离性读未提交(全局锁)由 Try 的预留程度决定无
性能中(全局锁有竞争)高高
适用事务长度短短长
补偿正确性框架自动业务保证业务保证
典型场景常规 CRUD 跨服务资金、库存等强约束履约、审批流

一句话取舍:能用 AT 就用 AT,AT 覆盖不了(复杂 SQL、强隔离、非事务资源)再上 TCC,流程长且补偿天然可逆就用 SAGA。


六、什么时候别用 / 别踩的坑

  1. 别用 AT 处理资金。读未提交的隔离性 + 全局锁重试,在资金场景下风险不可控。资金请用 TCC 或本地消息表。
  2. 别在 AT 事务里做 RPC 之外的副作用。发 MQ、调第三方支付、写 Redis,这些不在undo_log覆盖范围内,回滚不会撤销它们。
  3. 别忽略undo_log的清理。如果 TC 长时间不可用,undo_log会堆积,需要配置log_status和定时清理策略,否则表会膨胀。
  4. 别把全局锁当成数据库锁。它跨服务串行,热点数据(比如秒杀库存)在 AT 下会变成全局串行瓶颈。
  5. TCC 的 Cancel 必须能空回滚、必须幂等、必须防悬挂,这三条缺一不可,否则线上一定出数据不一致。
  6. SAGA 的补偿不是回滚,是"反向业务操作"。如果正向操作不可逆(比如已发短信、已扣积分),SAGA 就无能为力。

官方文档值得反复读的两处:Seata 官方文档、Apache Seata GitHub 仓库。源码里io.seata.rm.datasource.undo包下的AbstractUndoExecutor及其子类是理解 AT 回滚的最佳入口。


系列预告:单机并发和分布式事务都聊完了,下一篇我们转向分布式锁的三种实现(Redis / ZooKeeper / 数据库)在源码层面的取舍,重点拆 Redisson 看门狗续期和 RedLock 的争议。感兴趣可以关注,我们下篇见。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/29 21:32:47

2024年TensorFlow与PyTorch实战指南:安装、图像分类与部署选型

2016年我第一次跑TensorFlow是在一台老旧的CPU笔记本上,为了复现一个MNIST手写数字识别,光是搞明白Session和placeholder就折腾了三个晚上。多年过去,TensorFlow早就从1.x那种“先构图再执行”的写法变成了默认Eager执行的2.x,版本…

作者头像 李华
网站建设 2026/9/29 21:30:45

医用吊塔:手术室和 ICU 天花板上那根“臂“是做什么的

进过手术室或 ICU 的人可能会注意到:监护仪、呼吸机、输液泵并不都堆在地面上,而是集中挂在从天花板伸下来的一根可旋转的悬臂上,旁边还整齐排着氧气、负压吸引、压缩空气的接口。这套装置通常被称为医用吊塔,也有吊桥、悬臂等叫法…

作者头像 李华
网站建设 2026/9/29 21:30:45

Excel 水印方案对比:页眉图片 vs 背景图片(附 C# 实现)

在报表分发、合同归档、机密数据共享等场景中,给 Excel 文档添加水印是一种简单且实用的版权标识手段。水印既可以由图片构成,也可以由文字构成。对中文使用者来说,"机密"、"内部资料"、"严禁外传"等中文字样的…

作者头像 李华