☰
Seata 分布式事务核心原理详解(TC/TM/RM、AT/XA/TCC/Saga、锁机制)
2026/11/4 2:25:06 网站建设 项目流程

前言

在微服务架构下,业务会拆分到多个独立服务,跨服务操作就会遇到分布式事务问题,保证多个微服务的数据一致性。Seata 是目前主流的分布式事务框架,本文重点拆解 Seata 的三大核心组件 TC/TM/RM,AT模式完整执行时序、锁模型,补充 TCC 模式,以及 AT/XA/TCC/Saga 的适用场景对比,同时梳理开发中高频踩坑点。

Seata AT模式是业务开发最常用方案,本文案例均基于Seata。

一、Seata 三大核心组件:TC、TM、RM

Seata 架构由三个角色组成:TC事务协调者、TM事务管理器、RM资源管理器。

1. TC(Transaction Coordinator,事务协调者,Seata Server)

TC是独立部署的服务端进程,需要单独下载 seata-server 包启动。

  • 职责:存储全局事务、分支事务状态;接收TM/RM注册与状态上报;驱动所有分支事务执行二阶段提交/回滚,是全局事务的总指挥中心。
  • 存储模式:
    • file 文件存储:TC重启事务数据丢失,仅用于Demo测试
    • db数据库存储:将全局事务、分支事务、锁信息存入global_table/branch_table/lock_table,测试环境常用
    • raft集群模式:生产推荐,TC多节点集群,leader宕机自动选主,解决TC单点故障
  • ⚠️单点故障问题:
    TC宕机后,新的全局事务无法创建;已经开启但未完成的全局事务,二阶段提交/回滚无法执行,分布式事务卡住。
补充:已经完成一阶段提交的本地数据不会自动回滚,等TC服务恢复后,TC会定时扫描未完成事务,继续执行二阶段流程。

2. TM(Transaction Manager,事务管理器,客户端组件)

TM不是独立进程,是嵌入业务微服务的客户端组件,引入seata-spring-boot-starter就自带该能力。

  • 职责:开启全局事务(@GlobalTransactional),标记全局事务边界;业务执行完毕后,通知TC全局事务提交或回滚。
  • 特点:一次全局事务中,只有一个TM,也就是发起分布式事务的服务。

3. RM(Resource Manager,资源管理器,客户端组件)

RM同样是客户端组件,嵌入所有参与分布式事务的微服务。发起事务的服务可以同时是TM+RM。

  • 职责:管理本地数据库资源;向TC注册分支事务,上报分支执行状态;接收TC下发二阶段指令,执行本地分支提交或回滚(AT模式依靠undo_log实现补偿回滚)。
一句话通俗类比:
TC = 中央调度指挥部;RM = 各个业务节点(订单、库存);TM = 发起本次跨多节点操作的请求入口。

为什么不能让TM兼任TC?3个核心原因

1.TM所在业务服务随时可能宕机(最核心)
订单服务作为TM,如果同时充当TC。一阶段全部执行成功,准备执行二阶段时订单服务宕机。事务状态保存在业务服务内,库存、账户分支事务一阶段已经提交,无人触发二阶段,undo_log和锁无法清理,造成数据不一致。
独立TC场景:事务状态持久在TC,即使TM业务服务宕机,TC仍然存活,定时扫描未完成事务,等下游服务在线后继续下发二阶段指令,保证事务闭环。

核心原则:分布式事务状态必须保存在独立第三方,不能放在参与事务的业务节点上。业务节点会重启、下线、扩容。

2.TM只是事务参与者之一,无法拥有全部分支视图
一次事务链路:订单(TM)→库存(RM)→账户(RM)。TM只清楚自己本地分支情况,无法实时感知其他分支执行状态。网络分区场景下,TM收集信息不全,无法判断全局提交/回滚。
TC作为公共中心,所有RM上报状态,TC持有全部分支事务完整视图,统一仲裁全局结果。

3.业务服务频繁上下线,不适合承担状态持久与调度工作
订单、库存这类业务微服务,会频繁发布重启、弹性扩缩容,不适合长期保存事务状态,也不适合长时间重试调度。TC专门负责状态持久、定时扫描、重试调度,独立部署高可用集群,实现职责解耦。

和经典2PC对照:Seata TC就是原生2PC中的协调者;TM/RM属于参与者侧组件。如果TM兼任TC,就是参与者充当协调者,分布式系统中单点风险极高。

TM的存在意义:不能直接删掉TM,只留TC+RM

TC只会被动接收指令,保存事务状态,下发指令。TC不会主动感知业务什么时候开启事务。TM负责事务入口工作:

  1. 业务代码走到@GlobalTransactional方法,TM调用TC创建全局事务,拿到XID;
  2. XID放入调用链路,传递给下游所有微服务;
  3. 业务执行完成,TM向TC上报全局事务投票结果(提交/回滚);业务抛异常,通知TC全局回滚。

如果没有TM:没有组件申请全局事务、生成XID、传递XID;下游RM不知道当前属于哪个全局事务,无法注册分支。RM仅在收到XID后,才能注册分支、执行二阶段指令。

类比:TC是裁判,RM是球员,TM是教练。教练告诉裁判比赛开始,比赛结束上报结果;只有裁判+球员,比赛无法启动。
重要提醒:TM不是独立服务。引入seata starter的微服务自带TM+RM。发起事务的服务激活TM能力;下游被调用服务仅使用RM能力。

二、注解区分:@Transactional VS @GlobalTransactional(高频踩坑)

注解归属作用范围
@TransactionalSpring原生仅管控当前JVM本地数据库事务,感知不到远程微服务,无法实现分布式事务
@GlobalTransactionalSeata提供开启Seata全局分布式事务,底层封装TM逻辑,生成XID,串联所有RM分支事务

SpringBoot中 @EnableTransactionManagement 误区

SpringBoot自动装配DataSourceTransactionManagerAutoConfiguration,只要引入数据库相关依赖,SpringBoot自动注册事务管理器,自动开启AOP事务代理。

  • SpringBoot项目:@EnableTransactionManagement可省略,属于冗余注解,加不加不影响@Transactional本地事务生效;
  • SSM原生Spring项目:必须手动添加@EnableTransactionManagement开启事务AOP。
对Seata的@GlobalTransactional来说,启动类加不加@EnableTransactionManagement不影响全局事务。@GlobalTransactional会创建独立Seata事务切面,不依赖Spring原生事务AOP;但是分支内部仍然依赖Spring本地事务管理器。

✅工程标准写法(AT模式)

业务链路:A服务 → B服务 → C服务

  • A:请求入口,分布式事务发起方,添加@GlobalTransactional,同时具备TM+RM能力
  • B:A远程调用的中间服务,仅RM,只需要@Transactional
  • C:B远程调用的下游服务,仅RM,只需要@Transactional

场景1:前端下单请求,链路 A→B→C

// A服务(TM+RM)
@Service
public class OrderService {
// 全局事务入口,只用@GlobalTransactional,不要叠加@Transactional
@GlobalTransactional
public void createOrder(){
// 本地创建订单(RM分支)
orderMapper.insert();
// 远程调用B
bFeign.deduct();
}
}
// B服务(RM)
@Service
public class AccountService {
// 分支方法,保留Spring原生@Transactional
@Transactional
public void deduct(){
// 扣余额,远程调用C
cFeign.reduceStock();
}
}
// C服务(RM)
@Service
public class StorageService {
@Transactional
public void reduceStock(){
// 扣库存
}
}

场景2:后台管理接口,直接访问B服务,链路 B→C
此时B变成链路最外层入口,B上添加@GlobalTransactional,B作为TM;下游C依旧只保留@Transactional。这是一套独立全局事务,和场景1A发起的事务互不干扰。

强制编码规范(避坑)

  1. 全局入口方法,不要同时写@Transactional + @GlobalTransactional,切面优先级混乱,虽然偶尔能跑,禁止混用。入口只用@GlobalTransactional;
  2. 下游RM分支必须保留@Transactional。AT模式一阶段,业务SQL和undo_log写入必须在同一个本地事务,保证原子性;不加会导致undo_log写入失败,回滚失效;
  3. 仅链路最外层入口添加@GlobalTransactional,下游微服务不能添加@GlobalTransactional。下游添加会新建独立全局事务,不是同一个事务;
  4. 一个调用链路只能有一处@GlobalTransactional,多处会创建多个独立全局事务,引发异常。

三、AT模式完整执行时序(修正:先TM创建全局事务,再执行业务分支)

AT模式整体分为两大阶段:一阶段(准备)、二阶段(提交/回滚,TC驱动)

整体前置流程

  1. 调用A服务@GlobalTransactional方法,TM向TC申请创建全局事务,生成全局唯一XID;
  2. XID放入请求头,随远程调用传递到下游B、C;B、C拿到XID后激活RM;
  3. 各个服务RM执行本地分支事务(下面一阶段时序),每个分支独立执行下面步骤;

单分支一阶段完整时序(全部在同一个本地事务内,未commit)

  1. RM解析SQL,查询数据前镜像;
  2. 执行业务SQL(update/insert/delete);
  3. 查询数据后镜像;
  4. 插入undo_log回滚记录;
  5. RM调用TC注册分支事务,申请全局锁;
    • 拿锁失败:直接回滚本地事务,业务SQL、undo_log全部撤销;
  6. 拿到全局锁成功,执行本地事务commit;
  7. commit本地事务后,MySQL原生行锁释放,RM上报TC:分支一阶段执行成功。
核心设计目的:先生成undo_log,再注册分支,规避空回滚问题。
如果反过来:先注册分支拿锁,再执行业务SQL。SQL执行报错,TC已经记录分支,但RM没有undo_log,后续全局回滚时找不到回滚数据,触发空回滚异常。
同时,lockKey需要执行SQL拿到主键,构造表名+主键传给TC申请锁,未执行SQL前无法获取主键。

二阶段(TC统一调度)

TC收集所有分支一阶段结果,做全局仲裁:

  • ✅ 所有分支一阶段成功:TC下发提交指令给全部RM;RM收到指令,删除undo_log,二阶段完成。
  • ❌ 任意分支失败:TC下发回滚指令给全部RM;RM收到回滚指令后,先校验当前数据库真实数据:
    1. 如果数据库数据没有被其他事务修改:直接使用undo_log前镜像执行回滚,恢复数据,完成后删除undo_log;
    2. 如果数据库数据已经被其他业务修改(出现脏写冲突):不能直接覆盖数据,Seata会抛出异常,终止自动回滚流程,交由业务设置对应的人工处理/告警逻辑,防止覆盖第三方更新的数据,造成数据错乱。
⚠️重点面试考点:
AT二阶段回滚不是无脑覆盖,会先比对当前库数据与undo_log后镜像。一旦检测到数据被外部修改,自动回滚不再执行,避免强行覆盖别人已提交的数据。这种场景属于数据冲突,需要业务监控告警、人工介入处理。

四、并发场景:XA vs Seata AT锁阻塞差异

XA(传统2PC)

一阶段执行SQL,加MySQL行锁,事务不commit。行锁和数据库连接一直占用,直到二阶段TC下发commit/rollback才释放。

  • 阻塞位置:数据库内核层面;
  • 阻塞后果:长事务长时间占用数据库连接,连接池容易耗尽,数据库压力大;
  • 锁持有时间:整个分布式事务生命周期。

Seata AT全局锁阻塞

T2执行update、生成undo_log之后,在RM调用TC申请全局锁这一步被拦截。

T2业务SQL在本地事务内部执行成功,MySQL行锁短暂持有,但还没有commit。拿到锁失败直接rollback,本次修改不会持久到数据库。
  • 阻塞位置:业务代码和TC之间(应用层逻辑判断);
  • 阻塞特点:默认快速失败,不阻塞等待;锁重试需要手动配置参数开启;
  • 配置参数:seata.client.lock.retryTimes、seata.client.lock.retryInterval
⚠️风险:开启长时间锁重试,T2会持续持有MySQL行锁,占用数据库连接,丧失AT高性能优势,类似XA长锁问题。
对比项XA MySQL行锁阻塞Seata AT TC全局逻辑锁阻塞
阻塞位置MySQL数据库内TC应用层
阻塞时是否执行update✅已执行update,数据库锁等待✅事务内执行update,commit前拦截
是否长期占用数据库连接是默认不会(快速失败)
回滚代价需要回滚已执行SQL拿锁失败直接回滚,代价小

五、AT模式隔离级别:脏读、脏写

AT模式默认读未提交(Read Uncommitted)
假设T1一阶段完成,本地事务commit,库存更新为98,TC全局锁还持有,等待二阶段。

1.普通select查询(无@GlobalTransactional)
不受Seata管控,直接读取数据库已提交数据,可以读到库存=98。如果后续T1二阶段回滚,就出现脏读。

2.Seata全局事务T2,默认隔离级别
T2普通select同样读到脏数据;如果T2尝试update这一行,申请全局锁会被拦截,最终回滚,AT默认一定防止脏写。

3.手动开启Seata读已提交 @GlobalTransactional(isolation = Isolation.READ_COMMITTED)
T2查询时RM主动访问TC校验全局锁,如果该行存在全局锁,RM读取undo_log前镜像,返回修改前旧数据,避免脏读。

代价:查询增加一次TC远程调用+读取undo_log,性能下降。
总结:AT默认:允许脏读,禁止脏写;读已提交是应用层实现,非数据库原生隔离。

六、TCC模式(新增)

TCC 介绍

TCC(Try-Confirm-Cancel),是Seata支持的业务补偿型分布式事务,无数据库undo_log,完全由业务代码编写三段接口,属于侵入业务代码的方案。
TCC同样基于TC/TM/RM架构,TC负责全局事务状态保存与二阶段调度。
三个阶段:

  1. Try(预留资源,一阶段):检查资源,冻结/预留业务资源,不直接扣减真实数据。
    例:扣余额Try:校验余额,冻结余额,不扣实际账户余额
  2. Confirm(确认提交,二阶段成功分支):确认执行业务,使用Try阶段预留资源,正式提交。
    例:Confirm:把冻结的余额真正扣掉
  3. Cancel(回滚补偿,二阶段失败分支):释放Try阶段预留资源,恢复数据。
    例:Cancel:解冻余额,恢复冻结额度
一句话概括:Try做资源预留;Confirm确认提交;Cancel撤销释放。

TCC完整流程

  1. TM收到@GlobalTransactional,向TC创建全局事务,拿到XID;XID传递下游;
  2. 所有参与分支执行Try,预留资源,向TC注册分支;
  3. TC收集所有Try结果:
    • 全部Try成功:TC下发Confirm指令,所有分支执行Confirm,完成业务提交;
    • 任意Try失败:TC下发Cancel指令,所有分支执行Cancel,释放预留资源。

TCC优缺点

✅优点:

  1. 不依赖数据库undo_log,不绑定数据库,可以用于非数据库资源(调用第三方支付、外部API);
  2. 无AT的脏读问题,隔离性由业务自行控制;
  3. 没有全局锁,高并发场景性能可控。

❌缺点:

  1. 业务侵入极强:每一个业务操作,必须手动编写Try/Confirm/Cancel三套接口,开发工作量巨大;
  2. 需要处理幂等、空回滚、悬挂三大经典TCC异常问题,开发门槛高;
    • 空回滚:Cancel先于Try执行;
    • 悬挂:Try晚于Cancel执行;
    • 幂等:Confirm/Cancel重复调用,不能重复执行业务;
  3. 业务逻辑复杂,维护成本高。

适用场景:
跨第三方服务、非数据库资源、高性能核心链路,AT无法满足的场景。

七、Saga事务模式(长事务场景方案)

AT、XA不适合长事务(人工审批、履约流程)。AT虽然释放MySQL行锁,但TC全局锁会一直持有到二阶段结束,如果等待人工审批几天,全局锁占用多天,其他分布式事务无法修改该行,业务卡死。

Saga核心思想

将一个大全局事务拆分成一串独立本地子事务。每一步子事务执行完,立刻提交本地事务,释放数据库锁;后续步骤失败,执行配套反向补偿接口。全程不持有长锁。

案例:请假审批流程
创建请假单(本地事务提交,锁释放)→等待主管人工审批(等待几天,无任何锁)→审批通过执行人事登记;审批拒绝,执行补偿接口修改请假单状态为作废。

Saga两种实现模式

  1. 编排式(Choreography):服务之间互相发MQ消息,无中心协调器;缺点:链路复杂,维护困难。
  2. 编排式(Orchestration):存在中心Saga协调器驱动各个子服务,不一定依赖MQ。

Saga优缺点

✅优点:无长锁,适配人工审批、外部回调这类长事务场景;
❌缺点:

  1. 每个正向业务接口,必须手写配套补偿接口,开发成本高;
  2. 无隔离性,子事务提交后,数据立刻对外可见,中间态业务数据持久落库;
  3. 补偿接口执行时可能失败,需要人工对账兜底。
AT/XA追求尽量对外不可见中间状态;Saga天生阶段性提交,中间数据对外可见,仅保证最终一致性。业务开发需要识别中间状态,不能把流程未完成的数据当成有效数据。

八、XID传递(重要前置条件)

Seata依靠XID串联整个分布式事务链路。XID需要放在请求头跨微服务传输。

  • Feign:seata starter自动装配拦截器,自动传递XID,开箱即用;
  • RestTemplate:必须手动配置拦截器,否则XID无法传递,下游RM识别不到全局事务,分支不注册,异常无法回滚。
XID丢失后果:下游服务只是独立本地事务,出现数据不一致。

九、模式横向对比总结

模式原理锁特点适用场景业务侵入
ATundo_log自动补偿一阶段释放MySQL行锁,TC全局锁到二阶段结束微服务短事务,数据库场景,最常用低,几乎不用写额外业务代码
TCCTry/Confirm/Cancel三段业务接口业务层资源预留,无框架全局锁对接第三方API、非数据库资源、高性能链路极高,需要手写3套接口,处理空回滚/悬挂/幂等
Saga分步本地事务+反向补偿无长锁长事务,人工审批、履约流程中,编写补偿接口
XA数据库原生2PC长持有数据库行锁强一致性,并发低

总结

  1. TC是独立部署服务,全局事务状态存储与二阶段调度中心;TM/RM是客户端组件嵌入业务微服务;
  2. AT模式执行顺序:TM创建全局事务、生成XID → 各RM执行一阶段(镜像+undo_log+注册分支)→ TC收集结果驱动二阶段提交/回滚;二阶段回滚会校验当前数据库数据,数据未修改直接回滚;数据已被修改,抛出冲突异常,等待业务告警人工处理;
  3. AT适合短事务高性能场景;XA适合强一致性场景,锁占用时间长;TCC适合对接外部系统,侵入业务;Saga专门用于长事务、人工审批场景;
  4. 编码规范:链路入口加@GlobalTransactional,下游分支保留@Transactional;保证XID跨服务传递;禁止链路多处开启全局事务。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询