Spring boot 4 & JAVA 25 微服务事务实战:Seata 的 AT、TCC、Saga 、XA、Spring JDBC 事务 如何选型及使用

文章目录

  • 一、Seata的核心概念讲解
    • 核心角色
    • 核心概念
    • 全局事务生命周期
        • [1. 开启全局事务 (Begin)](#1. 开启全局事务 (Begin))
        • [2. XID 传播 (Propagation)](#2. XID 传播 (Propagation))
        • [3. 分支注册 (Registration)](#3. 分支注册 (Registration))
        • [4. 本地事务执行 (Local Execution)](#4. 本地事务执行 (Local Execution))
        • [5. 全局决议 (Commit/Rollback)](#5. 全局决议 (Commit/Rollback))
  • [二、Spring jdbc事务与Seata事务区别](#二、Spring jdbc事务与Seata事务区别)
    • [1. 作用范围与架构层级](#1. 作用范围与架构层级)
    • [2. 核心实现原理](#2. 核心实现原理)
    • [3. 数据一致性与隔离性](#3. 数据一致性与隔离性)
    • [4. 代码侵入性与配置](#4. 代码侵入性与配置)
    • [5. 异常处理与边界情况](#5. 异常处理与边界情况)
    • 总结对比表
  • 三、Seata中事务4种模式的区别:TA、TCC、SAGA、XA
  • [四、分布式事务 TCC 模式下的经典"三座大山"](#四、分布式事务 TCC 模式下的经典“三座大山“)
    • [1. 空回滚 (Null Rollback)](#1. 空回滚 (Null Rollback))
      • [问题场景:Cancel 比 Try 先到,或者 Try 根本没执行](#问题场景:Cancel 比 Try 先到,或者 Try 根本没执行)
      • [Seata 的解决方案:`tcc_fence_log` 表](#Seata 的解决方案:tcc_fence_log 表)
    • [2. 悬挂 (Suspend)](#2. 悬挂 (Suspend))
      • [问题场景:Cancel 先执行了,Try 后到(网络延迟导致的"迟到")](#问题场景:Cancel 先执行了,Try 后到(网络延迟导致的“迟到”))
      • [Seata 的解决方案:状态标记 `SUSPENDED`](#Seata 的解决方案:状态标记 SUSPENDED)
    • [3. 幂等 (Idempotence)](#3. 幂等 (Idempotence))
    • 总结:一张表解决所有问题
  • [五、AT + TCC的混合使用](#五、AT + TCC的混合使用)
    • [典型适用场景 (Use Cases)](#典型适用场景 (Use Cases))
      • [场景一:电商下单(扣库存 + 删购物车 + 加积分)](#场景一:电商下单(扣库存 + 删购物车 + 加积分))
      • [场景二:金融转账(余额支付 + 记账)](#场景二:金融转账(余额支付 + 记账))
      • 场景三:混合数据源操作
    • 坑
      • [1. 全局锁与业务锁的"死锁"风险(最致命的坑)](#1. 全局锁与业务锁的“死锁”风险(最致命的坑))
      • [2. AT 和 TCC 的回滚机制完全不同,混合使用容易导致开发者对"一致性"的误判。](#2. AT 和 TCC 的回滚机制完全不同,混合使用容易导致开发者对“一致性”的误判。)
      • [3. 超时与悬挂(Hanging)的复杂性](#3. 超时与悬挂(Hanging)的复杂性)
  • 六、SAGA模式
    • [SAGA 模式的使用场景](#SAGA 模式的使用场景)
      • [1. 跨系统的长周期业务(分钟级到天级)](#1. 跨系统的长周期业务(分钟级到天级))
      • [2. 涉及大量非数据库资源的操作](#2. 涉及大量非数据库资源的操作)
      • [3. 业务流程经常变更的柔性系统](#3. 业务流程经常变更的柔性系统)
    • [Saga 模式的 **"骨架"**](#Saga 模式的 “骨架”)
    • [SAGA 选型决策树](#SAGA 选型决策树)

在复杂的微服务系统中,单一的事务方案往往无法满足所有需求。Seata 提供的 AT、TCC、Saga、XA 及 Spring JDBC 事务模式,它们并非互斥,而是可以互补的积木,能够根据业务场景的差异灵活组合、按需选型,真正实现 "一致性、性能、开发效率"三者间的平衡。

一、Seata的核心概念讲解

要深入理解 Seata,必须先掌握它的三大角色 、核心概念 以及全局事务的生命周期。

核心角色

Seata 的架构由 1 个 Server 端 和 2 个 Client 端组成。

角色 全称 定位 核心职责
TC Transaction Coordinator (事务协调者) Server 端 (独立部署) 维护全局事务和分支事务的状态; 驱动全局提交或回滚; 管理全局锁。
TM Transaction Manager (事务管理器) Client 端 (代码注解) 定义全局事务的边界(开启、提交、回滚); 通常由业务服务中的 @GlobalTransactional 注解实现。 开始全局事务、提交或回滚全局事务。
RM Resource Manager (资源管理器) Client 端 (数据源代理) 管理分支事务处理的资源 与TC通信以注册分支事务和报告分支事务的状态 驱动分支事务提交或回滚。

核心概念

  1. XID (Transaction ID)
    • 定义:全局唯一的事务ID。
    • 作用:它是贯穿整个微服务调用链的"身份证"。TM 开启事务时生成,通过 RPC/RMI 透传给下游服务,RM 靠它来注册分支事务。
  2. Global Transaction (全局事务)
    • 定义:由 TM 发起的分布式事务整体。
    • 行为:它本身不直接操作数据,而是通过 XID 协调下属的所有分支事务。
  3. Branch Transaction (分支事务)
    • 定义:隶属于某个全局事务的本地事务。
    • 形态:每个参与分布式事务的微服务(RM)都会将自己的本地操作注册为一个分支事务。一个全局事务由多个分支事务组成。
  4. Transaction Group (事务分组)
    • 定义 :逻辑上的资源分组(如 my_test_tx_group)。
    • 作用:实现多环境隔离(开发/测试/生产)。不同分组的 TC 集群互不干扰。

全局事务生命周期

一个典型的分布式事务(以 AT 模式为例)的生命周期如下图所示: 详细介绍参见Seata 源码

1. 开启全局事务 (Begin)
  • 发起者 :TM(标注了 @GlobalTransactional 的服务)。
  • 动作:向 TC 发送 RPC 请求,申请开启一个全局事务。
  • 结果 :TC 生成一个全局唯一的 XID,并返回给 TM。
2. XID 传播 (Propagation)
  • 动作:TM 将 XID 绑定到当前线程上下文(ThreadLocal)。
  • 透传:在调用下游服务(RPC/HTTP)时,Seata 的过滤器会自动将 XID 放入请求头中,传递给下游服务。
3. 分支注册 (Registration)
  • 发起者:RM(下游服务的数据源代理)。
  • 动作 :下游服务收到请求后,解析出 XID。RM 在执行本地 SQL 前,会向 TC 发送注册请求,将当前的本地事务注册为一个分支事务。
  • 关键点 :TC 会为该分支事务分配一个 branch_id,并检查全局锁是否冲突。
4. 本地事务执行 (Local Execution)
  • 动作 :
    • AT 模式 :RM 拦截 SQL,生成 before_image 和 after_image 并写入 undo_log 表,然后提交本地事务(释放数据库锁,但持有全局锁)。
    • TCC 模式 :执行业务自定义的 Try 方法。
5. 全局决议 (Commit/Rollback)
  • 发起者:TM。
  • 动作 :当所有业务逻辑执行完毕,TM 根据是否有异常,向 TC 发起 全局提交 或 全局回滚 的决议。
  • 协调 :
    • 提交 :TC 异步通知所有 RM 删除 undo_log(AT 模式)或执行 Confirm(TCC 模式)。
    • 回滚 :TC 通知所有 RM 根据 undo_log 回滚数据(AT 模式)或执行 Cancel(TCC 模式)。

二、Spring jdbc事务与Seata事务区别

结合了 Spring JDBC 的底层原理与 Seata 的分布式事务机制,从作用范围、实现原理、数据一致性、异常处理 等维度针对两者核心区别对比进行了梳理:

1. 作用范围与架构层级

  • Spring JDBC 事务(本地事务)
    • 范围 :局限于单个 JVM 进程 和单个数据库实例。
    • 层级 :工作在 DAO 层 ,本质上是对 JDBC Connection 的封装。
    • 依赖:完全依赖于数据库自身的事务能力(如 MySQL 的 InnoDB 引擎)。
  • Seata 事务(分布式事务--跨多个数据源)
    • 范围 :跨越多个微服务进程 和多个数据库实例。
    • 层级 :工作在 RPC/服务调用层,是一个独立的中间件(由 TC、TM、RM 三部分组成)。
    • 依赖 :不完全依赖数据库的分布式能力,而是通过自己的 Undo Log 或 TCC 补偿逻辑来实现回滚。

2. 核心实现原理

  • Spring JDBC
    • 机制 :基于 AOP 代理 。它通过 DataSourceTransactionManager 拦截方法,在方法执行前调用 Connection.setAutoCommit(false),执行成功后调用 commit(),异常时调用 rollback()。
    • 回滚手段 :利用数据库原生的 Undo Log 进行物理回滚。
  • Seata (以 AT 模式为例)
    • 机制 :基于 两阶段提交 (2PC) 的演变 。
      • 一阶段 :业务 SQL 和 Undo Log (数据镜像)在同一本地事务中提交。此时数据已真实更新,但 Seata 会持有全局锁。
      • 二阶段 :
        • 提交:异步快速删除 Undo Log。
        • 回滚:根据 Undo Log 中的镜像,生成反向 SQL 进行补偿更新(覆盖数据)。
    • 回滚手段:应用层通过 SQL 撤销数据,而非数据库内部回滚。

3. 数据一致性与隔离性

  • Spring JDBC
    • 一致性 :提供 强一致性 (ACID)。
    • 隔离性:依赖数据库的隔离级别(如可重复读、串行化),通过数据库锁(行锁、表锁)实现。
  • Seata
    • 一致性 :AT 模式提供最终一致性(二阶段提交是异步的);XA 模式提供强一致性(但性能差)。
    • 隔离性 :
      • 读隔离 :Seata AT 模式默认是"读未提交"(Read Uncommitted),为了保证一致性,通常需要使用 SELECT FOR UPDATE 来申请全局锁。
      • 写隔离 :通过 全局锁 防止脏写,而不是数据库原生的锁。

4. 代码侵入性与配置

  • Spring JDBC
    • 注解 :使用 @Transactional。
    • 配置 :配置普通的 DataSource(如 HikariCP)和 DataSourceTransactionManager。
    • 侵入性:低,通常只需加注解。
  • Seata
    • 注解 :全局入口使用 @GlobalTransactional,分支事务通常仍保留 @Transactional。
    • 配置 :必须配置 DataSourceProxy(数据源代理)和 Seata 的 TransactionManager,且需要独立部署 Seata Server (TC)。
    • 侵入性:中等。AT 模式对业务代码无侵入(但需建表),TCC/Saga 模式需要编写补偿逻辑。

5. 异常处理与边界情况

  • Spring JDBC
    • 异常:处理数据库约束异常(如唯一键冲突)、连接超时等。
    • 边界:只关注单库的 ACID,不考虑网络分区或跨服务调用失败。
  • Seata
    • 异常 :需处理复杂的分布式特有问题:
      • 空回滚:Try 未执行,Cancel 先到了。
      • 业务悬挂:Cancel 执行了,迟到的 Try 才来。
      • 幂等性:网络重试导致的重复提交。
    • 边界:必须考虑网络故障、TC 服务宕机、分支事务状态不一致等极端情况。

总结对比表

维度 Spring JDBC 事务 Seata 事务 (AT 模式)
本质 数据库本地事务的封装 分布式事务协调器
核心机制 JDBC Connection + 数据库锁 两阶段提交 + Undo Log + 全局锁
一致性 强一致性 (ACID) 最终一致性
回滚方式 数据库 Undo Log 物理回滚 应用层反向 SQL 补偿
注解 @Transactional @GlobalTransactional (入口)
配置组件 DataSource + DataSourceTransactionManager DataSourceProxy + Seata Server
适用场景 单体应用、单数据库微服务 跨服务、跨数据库的微服务架构
一句话总结:
Spring JDBC 事务是"单兵作战"的武器,管好自己连接的那一个数据库;
Seata 是"联合作战"的指挥官,协调多个 Spring JDBC 事务(或其他资源)共同进退。

三、Seata中事务4种模式的区别:TA、TCC、SAGA、XA

Seata 的四种事务模式(AT、TCC、SAGA、XA)本质上是为了解决不同场景下的分布式事务问题。它们在代码侵入性 、一致性强度 、性能 和适用场景上有显著区别。

为了让你更直观地理解,总结了一个核心对比表,随后详细拆解每种模式。

核心对比一览表

特性 AT 模式 (默认推荐) TCC 模式 SAGA 模式 XA 模式
代码侵入性 无侵入 (仅注解) 高侵入 (需写3个接口) 中侵入 (需写补偿逻辑) 无侵入 (仅注解)
一致性 最终一致性 最终一致性 最终一致性 强一致性
性能 ⭐⭐⭐⭐ (一阶段提交) ⭐⭐⭐⭐ (无数据库锁) ⭐⭐⭐ (长流程) ⭐ (锁持有到第二阶段)
实现原理 两阶段 + 自动生成回滚日志 两阶段 + 手动预留/确认/取消 长事务 + 补偿回滚 两阶段提交协议
适用场景 通用微服务 (CRUD) 复杂业务/非数据库资源 长流程/异步化业务 金融核心/强一致要求

详细模式解析

1. AT 模式 (Automatic Transaction)

  • 定位 :默认推荐,对业务无侵入的通用解决方案。
  • 核心原理 :
    • 一阶段 :你执行 SQL 修改数据时,Seata 会通过数据源代理,自动生成一条 undo_log 记录(包含修改前后的镜像)。业务数据和回滚日志在同一个本地事务中提交。
    • 二阶段 :
      • 提交 :异步删除 undo_log。
      • 回滚 :根据 undo_log 中的镜像,生成反向 SQL 恢复数据。
  • 优点 :开发几乎不用改代码,只需加 @GlobalTransactional 注解。
  • 缺点 :依赖数据库(目前支持 MySQL, Oracle, PostgreSQL 等),且 undo_log 表会带来轻微性能损耗。
  • 适用:绝大多数基于关系型数据库的微服务场景。
  • 目标:在分布式全局事务中,通过数据库层面"行锁 + undo_log 回滚信息"保证分支事务的可回滚性与并发一致性
  • 关键机制 :
    • 在全局事务中,客户端拦截 SQL 并收集 undo 信息(before/after image 或逆向 SQL),保存在ConnectionContext(内存)。
    • 提交时先向 TC 注册分支(branchRegister),把 undo_log 刷入业务库,然后提交本地 DB;失败则在 Phase‑2 回滚时通过 undo_log 恢复数据。
    • 回滚时(Phase‑2),RM 从 undo_log 读取并执行逆向 SQL,然后删除对应 undo_log 记录。
undo_log表结构

详见 mysql undo_log表结构,

目前支持mysql、pg、kingbase、dm、sqlserver、oracle等传统关系型数据库

sql 复制代码
-- for AT mode you must to init this sql for you business database. the seata server not need it.
CREATE TABLE IF NOT EXISTS `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 COMMENT ='AT transaction mode undo table';
ALTER TABLE `undo_log` ADD INDEX `ix_log_created` (`log_created`);

2. TCC 模式 (Try-Confirm-Cancel)

  • 定位 :高性能、灵活的业务自定义模式。
  • 核心原理 : TCC 模式将一个分布式业务操作拆成三步,需要开发者手动实现三个逻辑:
    • Try:预留/准备资源(不做最终提交),尝试执行。检查业务规则,并预留资源(如:冻结库存)
    • Confirm:真正提交(释放预留并完成业务):确认执行。真正提交业务(如:扣减冻结的库存)。通常要求 Confirm 一定成功。
    • Cancel:回滚/释放预留(如果失败或超时):取消执行。释放预留的资源(如:解冻库存)。
  • 优点 :性能极高(Try 阶段只预留资源,不长期持有数据库锁),支持非数据库资源(如 Redis、MQ、第三方接口)。
  • 缺点 :代码侵入性大,需要为每个业务写三套逻辑。需要处理幂等 、空回滚 、悬挂三大难题(Seata 1.5.1+ 提供了 TCC Fence 自动解决)。
  • 适用:对性能要求极高、涉及非事务性资源(如 Redis)、复杂业务流程(如电商下单)。
  • 适合场景 :
    • 业务需要显式的"预留 + 确认/取消"流程(例如库存预扣、航班座位预留、分布式消息事务等)
    • 无法或不想依赖 DB 行级 undo_log(AT 模式)来回滚,但业务能实现幂等的 Confirm/Cancel 操作
  • 不适合场景:无法实现幂等或无状态的 confirm/cancel;或 Try 无法做到资源隔离/预留
tcc fence log

目前支持mysql、pg、oracle、oceanbase关系型数据库

sql 复制代码
CREATE TABLE IF NOT EXISTS `tcc_fence_log`
(
    `xid`           VARCHAR(128)  NOT NULL COMMENT 'global id',
    `branch_id`     BIGINT        NOT NULL COMMENT 'branch id',
    `action_name`   VARCHAR(64)   NOT NULL COMMENT 'action name',
    `status`        TINYINT       NOT NULL COMMENT 'status(tried:1;committed:2;rollbacked:3;suspended:4)',
    `gmt_create`    DATETIME(3)   NOT NULL COMMENT 'create time',
    `gmt_modified`  DATETIME(3)   NOT NULL COMMENT 'update time',
    PRIMARY KEY (`xid`, `branch_id`),
    KEY `idx_gmt_modified` (`gmt_modified`),
    KEY `idx_status` (`status`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;

3. SAGA 模式

  • 定位 :长事务解决方案,适合由多个子事务组成的复杂流程。
  • 核心原理 :
    • 将一个大事务拆分为多个按顺序执行的本地事务。
    • 如果某个步骤失败,SAGA 会按照相反的顺序,执行之前所有步骤的补偿操作(Compensate)。
    • 通常通过状态机来定义流程和对应的补偿逻辑。
  • 优点:服务间解耦,适合长时间运行的业务(如订单履约、物流调度)。
  • 缺点:业务逻辑需要拆分,开发复杂度较高。在回滚时,中间状态可能已被其他服务读取(弱一致性)。
  • 适用:业务流程长、涉及多个服务、允许异步执行的场景。
表定义

参见saga db schema

4. XA 模式

  • 定位 :传统的强一致性解决方案。
  • 核心原理 :基于标准的 XA 协议(两阶段提交)。
    • 一阶段 :所有分支事务执行 SQL,但不提交(数据库持有行锁)。
    • 二阶段:TC 通知所有分支提交或回滚。
  • 优点 :强一致性(ACID),只要事务提交,数据立即一致。理论最严谨。
  • 缺点 :性能差(因为一阶段完成后,锁会一直持有到二阶段结束,阻塞其他事务)。
  • 适用:对一致性要求极高、无法接受任何中间状态的场景(如银行核心账务系统)。

注意:需要数据库实现了XA接口,目前仅支持 MySQL, Oracle and MariaDB

选型建议

  1. 首选 AT 模式:如果你的业务是标准的 CRUD,基于 MySQL/Oracle/PG,且对性能要求不是变态级的,AT 模式是最佳选择(开发成本最低)。
  2. 性能瓶颈选 TCC:如果并发极高,或者涉及 Redis/文件存储等非数据库资源,或者业务逻辑复杂无法用 SQL 直接描述,请使用 TCC。
  3. 长流程选 SAGA:如果业务流程长达几分钟甚至几小时(如请假审批流、供应链流转),请使用 SAGA。
  4. 金融级强一致选 XA:如果你是做银行转账核心系统,绝不允许"转账中"的状态被看到,且愿意牺牲性能来换取绝对一致性,请使用 XA。

四、分布式事务 TCC 模式下的经典"三座大山"

空回滚、悬挂、幂等是分布式事务 TCC 模式下的经典"三座大山" 。它们通常由网络不稳定(超时、重传、乱序)引起。

Seata 在 1.5.1 版本之前,这些问题需要开发者自己通过建表、写逻辑来解决,代码侵入性很强。但从 Seata 1.5.1 版本开始 ,引入了 TCC Fence(TCC 篱笆)机制 ,通过一个统一的事务控制表 tcc_fence_log在框架层面(而非业务代码层面)完美解决了这三个问题。

1. 空回滚 (Null Rollback)

问题场景:Cancel 比 Try 先到,或者 Try 根本没执行

想象一下:全局事务开始了,但因为网络抖动或机器宕机,Try 方法根本没执行成功(比如 RPC 调用失败了)。但是,全局事务超时了,TC(事务协调者)决定回滚,于是发起了 Cancel 调用。

  • 后果 :如果 Cancel 方法不做判断,直接执行"解冻库存"逻辑,而实际上库存根本没冻结(Try 没执行),这就会导致资损(比如库存变成负数,或者多退了钱)。
  • 本质:Cancel 方法需要判断:"我对应的 Try 执行了吗?"

Seata 的解决方案:tcc_fence_log 表

Seata 引入了一张表 tcc_fence_log。

  1. Try 阶段 :只要 Try 方法一执行成功,Seata 就会往这张表里插入一条记录,状态标记为 TRIED
  2. Cancel 阶段 :在执行 Cancel 逻辑前,先查这张表。
    • 如果查不到记录 ➜ 说明 Try 根本没执行(或者执行失败了)。
    • 动作 :直接空回滚(什么都不做,直接返回成功),避免资损。

2. 悬挂 (Suspend)

问题场景:Cancel 先执行了,Try 后到(网络延迟导致的"迟到")

空回滚的"孪生兄弟"。

场景是:Try 方法调用时网络拥堵,导致全局事务超时,TC 发起了 Cancel。Cancel 执行完了,全局事务结束了。但是,那个拥堵的 Try 请求终于到达了服务器,并且成功执行了(预留了资源)。

  • 后果 :资源被预留了(比如库存冻结了),但后续再也没有 Confirm 或 Cancel 来释放它。这笔资源就永久"悬挂" 在那里,既不能用也不能退。
  • 本质:Try 方法需要判断:"在我来之前,是不是已经有人(Cancel)把事务给结束了?"

Seata 的解决方案:状态标记 SUSPENDED

  1. Cancel 阶段 :当 Cancel 执行时,如果发现 tcc_fence_log 表里没有记录(即空回滚场景),Seata 不仅不报错,反而会插入一条状态为 SUSPENDED 的记录。
  2. Try 阶段 :当那个"迟到"的 Try 到来时,它首先会检查 tcc_fence_log 表。
    • 如果发现状态是 SUSPENDED ➜ 说明这个全局事务已经被取消了。
    • 动作 :Try 方法拒绝执行,直接返回失败。从而防止资源被错误地预留。

3. 幂等 (Idempotence)

问题场景:重复提交或回滚

在网络超时或抖动时,TC 可能没收到 RM(资源管理器)的确认响应,于是 TC 会重试发送 Confirm 或 Cancel 指令。

  • 后果:如果你的 Cancel 方法写的是"扣减库存 100",第一次执行成功了,第二次重试又执行一次,库存就扣了 200。这就是严重的数据错误。
  • 本质:Confirm/Cancel 必须保证"无论调用多少次,结果一致"。

Seata 的解决方案:状态机控制

  1. 状态记录 :tcc_fence_log 表里有一个 status 字段。
  2. 执行流程 :
    • Confirm/Cancel 执行前 :检查表里是否有记录且状态是否已经是 COMMITTED 或 ROLLBACKED。
    • 如果是 :直接返回成功,不再执行业务逻辑。
    • 如果不是 :执行业务逻辑,执行成功后将状态更新为 COMMITTED 或 ROLLBACKED。

总结:一张表解决所有问题

Seata 1.5.1+ 通过 tcc_fence_log 这张表,利用数据库的唯一索引和状态字段,完美闭环了这三个问题:

问题 触发原因 Seata 的防御手段 核心逻辑
空回滚 Try 失败/未执行,Cancel 先到 查状态 Cancel 执行前查表,无记录则直接返回(防资损)。
悬挂 Try 迟到,Cancel 已执行 插标记 Cancel 空回滚时插一条 SUSPENDED 记录;Try 到来时若发现此标记,则拒绝执行。
幂等 网络重试导致多次调用 状态机 Confirm/Cancel 执行前先看状态,已完结则不再执行业务代码。

实操建议 :

如果你使用的是 Seata 1.5.1 及以上版本,只需要在 @TwoPhaseBusinessAction 注解中配置 useTCCFence = true,并建好 tcc_fence_log 表,框架就会自动帮你处理上述所有逻辑,你只需要关注业务本身即可。

五、AT + TCC的混合使用

AT + TCC 的混合使用 是 Seata 生产环境的"黄金搭档"。

这种混合并非技术上的耦合,而是一种基于业务场景的分层选型 。

核心思想是:让 AT 模式处理"简单的 CRUD",让 TCC 模式处理"复杂的资源预留"。

  • 不要试图在同一个服务里同时开启 AT 和 TCC(除非做特殊降级)。
  • 应该 在微服务架构层面,根据服务的核心程度 和资源类型 进行选型:
    • 非核心、简单 CRUD、快速上线 -> AT。
    • 核心资金/库存、需预占资源、高并发 -> TCC 。
      通过这种组合,你既不需要为了强一致性去承受全量 TCC 的开发成本,也不需要为了开发效率去承受 AT 模式在核心业务上的数据风险。

典型适用场景 (Use Cases)

这种混合模式在以下场景中是"最佳实践":

场景一:电商下单(扣库存 + 删购物车 + 加积分)

  • 购物车 & 积分服务 :使用 AT 模式。因为它们对隔离性要求不高,且逻辑简单。
  • 库存服务 :使用 TCC 模式。因为需要防止超卖,必须先"冻结"库存,不能直接扣减。
  • 流程 :用户下单 -> AT 事务开始 -> 购物车删除商品(AT) -> 库存冻结(TCC Try) -> 支付回调 -> AT 事务提交 -> 库存扣减(TCC Confirm)。

场景二:金融转账(余额支付 + 记账)

  • 记账服务 (Journal) :使用 AT 模式。流水记录一旦生成很难回滚,且是追加写。
  • 账户服务 (Account) :使用 TCC 模式 。先 Try 预扣金额(检查余额并冻结),支付成功后 Confirm 实际扣款。
  • 流程 :转账请求 -> AT 事务 -> 账户预扣(TCC Try) -> 生成流水(AT) -> 对方入账(AT) -> 账户扣款(TCC Confirm)。

场景三:混合数据源操作

  • MySQL 操作 :使用 AT 模式。
  • Redis / MQ 操作 :使用 TCC 模式 。因为 AT 模式主要针对关系型数据库,对 Redis 或 MQ 的事务支持较弱。此时可以封装一个 TCC 接口,在 Try 中操作 Redis,在 Cancel 中回滚 Redis。

坑

1. 全局锁与业务锁的"死锁"风险(最致命的坑)

这是 AT 和 TCC 混合时最常见的性能杀手。

  • 坑在哪里 :
    • AT 模式 依赖 Seata 的全局锁(Global Lock)来保证隔离性。
    • TCC 模式 通常在 Try 阶段会加业务锁(如数据库行锁、Redis 分布式锁)来冻结资源。
  • 避坑 :严格控制全局锁的等待时间(defaultGlobalTransactionTimeout),或者在 TCC 的 Try 阶段尽量使用无锁方案(如基于 Redis Lua 的原子操作),避免持有数据库行锁过久

2. AT 和 TCC 的回滚机制完全不同,混合使用容易导致开发者对"一致性"的误判。

  • 坑在哪里 :
    • AT 是"后置回滚" :数据在一阶段就真实提交了,二阶段回滚是通过 undo_log 生成反向 SQL 覆盖数据。
    • TCC 是"前置预留":一阶段只是冻结/预留,二阶段才是真实提交或释放。
  • 数据不一致场景 :
    • 假设流程是:Service A (AT) -> Service B (TCC Try)。
    • 如果 AT 成功提交,TCC Try 也成功冻结了资源。
    • 此时如果全局事务回滚,AT 会通过反向 SQL 把数据改回去,但 TCC 会执行 Cancel 释放资源。
    • 风险 :如果 AT 的反向 SQL 执行失败(如数据库主从延迟、SQL 语法不兼容),而 TCC 的 Cancel 已经成功,会导致业务状态(TCC)已回滚,但记账数据(AT)却没回滚干净,造成资损。
  • 避坑:核心资金/库存服务坚决用 TCC,非核心日志/通知服务用 AT。尽量避免"核心数据"由 AT 模式管理。

3. 超时与悬挂(Hanging)的复杂性

TCC 模式特有的"悬挂"问题,在混合调用时更难排查。

  • 坑在哪里 :
    • 当 AT 事务调用 TCC 服务时,如果网络出现"抖动"或"超时重试"。
    • 场景:AT 发起调用,TCC 的 Try 成功了,但响应包丢了。AT 以为 Try 失败,直接回滚(触发 TCC Cancel)。
    • 随后:那个"迟到"的 Try 请求到达(网络恢复),此时 Cancel 已经执行完了。
    • 结果 :Try 成功执行(预留了资源),但后续没有 Confirm 也没有 Cancel。这就是悬挂。
  • 避坑 :必须开启 Seata 的 TCC Fence(篱笆)机制 (即 tcc_fence_log 表)。它能自动拦截这种"迟到"的 Try 请求,防止悬挂。

六、SAGA模式

SAGA 模式是处理长周期事务 和复杂业务流程 的利器,但它也是最容易让开发者"踩坑"的模式,因为它的设计哲学是牺牲隔离性来换取性能和柔性。

SAGA 模式的使用场景

SAGA 的核心公式是:长事务 + 最终一致性 + 补偿机制 。

当你遇到以下场景时,SAGA 往往是比 TCC 或 AT 更好的选择:

1. 跨系统的长周期业务(分钟级到天级)

这是 SAGA 的主场。如果业务流程涉及人工审核、物流运输、第三方回调(如银行转账),耗时可能长达几分钟甚至几天。

  • 典型例子:旅游预订(订机票+订酒店+租车)、供应链采购(下单+生产+发货+签收)。

2. 涉及大量非数据库资源的操作

当你的事务不仅操作数据库,还要发短信、发邮件、调用外部 API、操作文件系统时。

  • 典型例子:用户注册成功后,发欢迎邮件、初始化用户文件目录、赠送新人积分。

3. 业务流程经常变更的柔性系统

  • 典型例子:电商促销流程,可能今天要先发券再扣库存,明天要先抽奖再扣库存。

Saga 模式的 "骨架"

这三张表是Saga 模式的 "骨架" ,解决了 "流程怎么跑" 和 "状态不丢失" 的问题(即可靠性 和幂等性 的底层支撑),保证了事务流程不会断、不会丢。

但 "血肉"(业务表的加减乘除、冻结字段设计)还是需要你来写的,否则就会掉进"脏读/脏写"的坑里。

  • seata_state_machine_def (Definition) - "流程图纸"
    • 作用 :它定义了模板。就像编译好的代码,告诉 Seata 一个事务应该长什么样。
    • 解决的坑 :流程定义的存储。保证你写的那个 JSON 流程不会丢,服务重启后还能读取到。
  • seata_state_machine_inst (Instance) - "订单档案"
    • 作用 :它记录了某一次具体事务的全局上下文。
    • 解决的坑 :
      • 全局事务追踪 :通过 id 或 business_key,你可以查到整个事务跑得怎么样了。
      • 幂等性基础 :如果同一个 business_key 重复提交,框架可以通过查询这张表发现"哎,这个订单号已经有一个实例在跑了",从而拒绝重复提交。
  • seata_state_inst (State Instance) - "执行日志"
    • 作用 :记录每一个节点的执行细节。
    • 解决的坑 :
      • 断点续跑 :如果服务在执行某个节点时宕机了,重启后 Seata 会根据这张表里记录的 status,发现某个节点还在 RU(运行中)但没有后续日志,就会触发回滚或重试。
      • 回溯排查:出了问题,看这张表就能知道是卡在哪个服务调用上了。

SAGA 选型决策树

  • 业务流程是否超过 1 分钟? (如物流、审批)
  • 是否涉及非数据库操作? (发短信、调第三方)
  • 是否能容忍中间状态(脏读)?
    都是的情况下,才考虑使用SAGA

SAGA 是一把双刃剑,它能解决长事务的性能问题,但把数据一致性的责任完全交给了业务代码。除非你有把握写好补偿逻辑和处理幂等,否则不要轻易在核心资金链路使用。

相关推荐
写后端的胖头鱼6 天前
一文详解 MVCC/隔离级别/引入Seata的影响
seata·mvcc·rc·脏读·全局脏读
九皇叔叔22 天前
Seata——把分布式事务理论落到 Java 微服务实践
分布式·分布式事务·cap·base·saga
九皇叔叔25 天前
从本地事务到分布式事务
java·分布式·分布式事务·cap·base
Cerman1 个月前
Intel TCC模式下TSC与ART确定性线性关系设计原理
x86·intel·时钟同步·tsc·tcc·art
爱学习的小可爱卢2 个月前
Seata AT模式:面试与项目答辩全解析
seata·springcloud
硅农深芯2 个月前
半导体阻挡层选材逻辑:为何W适配Ti/TiN、Cu适配Ta/TaN
ti·ta·阻挡层
折哥的程序人生 · 物流技术专研3 个月前
Java面试通关⑭:分布式理论与架构全集
分布式·微服务·校招·分布式事务·cap·java面试·最终一致性
清心歌3 个月前
Seata AT 模式简单学习及总结
分布式·seata
南部余额4 个月前
Seata分布式事务解决方案
seata·at·xa·saga·tcc
小马爱打代码4 个月前
事务处理最佳实践:单库 → 跨库 → 跨应用
分布式事务