分布式事务与 Seata AT

一、分布式事务要解决什么

单体架构里,一个 @Transactional 就能让多张表的操作原子提交或回滚,靠的是单个数据库的本地事务。

但微服务下,一次下单要跨三个服务:

  • 订单服务:创建订单
  • 库存服务:扣减库存
  • 支付服务:扣款

每个服务有自己独立的数据库,订单库的事务管不了库存库,更管不了支付库。“订单创建成功但库存没扣”或“钱扣了订单没成”,就是分布式事务要解决的问题。


二、Seata 的三大角色:谁发起、谁决策、谁执行

Seata 把分布式事务拆成三个角色协作,用一个下单场景贯穿:

角色 全称 部署位置 通俗理解 在下单场景里是谁
TM Transaction Manager 嵌入业务应用 发起人,定义全局事务边界 订单服务(入口)
TC Transaction Coordinator 独立部署的 Server 总指挥,维护全局事务状态,驱动提交/回滚 Seata Server
RM Resource Manager 嵌入业务应用 执行者,管理分支事务,执行二阶段动作 库存服务、支付服务

关键认知:TC 是独立服务,不碰业务数据;TM 和 RM 是 jar 包,跟业务应用一起跑。 三者通过长连接通信,用 XID(全局事务 ID) 串联。


三、完整流程:一条 XID 串起 TM、TC、RM

还是下单场景:订单服务(TM)→ 库存服务(RM)→ 支付服务(RM)。

第 1 步:TM 向 TC 申请开启全局事务

订单服务作为入口,TM 向 TC 发起 begin。TC 生成一个全局唯一的 XID,作为这次分布式事务的“身份证”。

TM 动作:发起 begin。TC 动作:生成 XID,记录全局事务状态为“开始”。

第 2 步:XID 沿调用链向下传递

订单服务调用库存服务、支付服务时,XID 被自动传递下去(通过 RPC 上下文),确保所有参与者都知道“我们属于同一个全局事务”。

第 3 步:每个 RM 向 TC 注册分支事务

库存服务、支付服务在执行本地操作前,带着 XID 向 TC 注册分支事务,把自己的本地事务挂到全局事务名下。

RM 动作:注册分支,拿到 Branch ID。TC 动作:记录这个全局事务下有哪些分支。

第 4 步:RM 执行本地事务,并向 TC 申请全局锁

这是 AT 模式的核心。每个 RM 执行本地 SQL 时,Seata 通过代理数据源在背后做四件事:

  1. 解析 SQL:拿到操作类型、表名、条件。
  2. 查前镜像:查出修改前的数据。
  3. 执行业务 SQL:正常更新。
  4. 查后镜像:查出修改后的数据。

然后,把前镜像、后镜像、SQL 信息打包成 undo log,插入 UNDO_LOG 表。

关键一步:在本地事务提交之前,RM 必须先向 TC 申请这条记录的全局锁。拿到全局锁,才能提交本地事务;拿不到,就回滚本地事务、释放本地锁,然后重试。

RM 动作:申请全局锁 → 拿到后,把业务更新和 undo log 在同一个本地事务中一起提交,释放本地数据库锁。TC 动作:记录全局锁归属。

一阶段结束时:数据已经真实写入了数据库,但全局锁还攥在 TC 手里。其他全局事务想改同一行数据,过不去。

第 5 步:TM 向 TC 发起全局决议

所有分支执行完毕后,订单服务的 TM 根据整体结果告诉 TC:这个 XID 对应的全局事务是提交还是回滚。

TM 动作:发起 commit 或 rollback。TC 动作:根据决议,驱动所有 RM 执行二阶段。

第 6 步:TC 驱动所有 RM 执行二阶段动作

如果全局提交:TC 通知各 RM 提交。RM 把这个请求丢进异步队列,马上返回成功,之后异步批量删除 undo log。因为一阶段数据已经落库了,二阶段提交几乎没有成本。

如果全局回滚:TC 通知各 RM 回滚。RM 开启一个本地事务:

  1. 找到 undo log(通过 XID 和 Branch ID)。
  2. 数据校验:拿 undo log 里的后镜像和当前数据库数据对比,一致才安全回滚。
  3. 生成反向 SQL 并执行:根据前镜像把数据改回去,提交本地事务,上报 TC。

RM 动作:提交则异步删 undo log;回滚则按 undo log 反向补偿。TC 动作:确保所有分支都完成二阶段。


四、把 TM、TC、RM 在每个阶段的动作汇总

阶段 TM(订单服务) TC(Seata Server) RM(库存/支付服务)
开启 发起 begin 生成 XID,记录全局事务 —
注册 — 记录分支 带 XID 注册分支,拿 Branch ID
一阶段 — 管理全局锁 执行 SQL、生成 undo log、申请全局锁、提交本地事务
决议 发起 commit/rollback 接收决议,准备驱动二阶段 —
二阶段 — 通知各 RM 执行 提交则异步删 undo log;回滚则按 undo log 反向补偿

一句话串起来:TM 发起,TC 决策,RM 执行,XID 串联。 一阶段 RM 先提交本地事务但攥着全局锁,二阶段 TC 根据 TM 的决议驱动 RM 异步提交或反向补偿。


五、AT 模式的一个关键权衡:全局锁与隔离性

AT 的默认全局隔离级别是“读未提交”——因为一阶段本地事务已经提交,其他事务在数据库层面能读到中间状态数据。

Seata 的补救方式:只有带 SELECT FOR UPDATE 的查询才会去申请全局锁。如果全局锁被别的全局事务持有,这个查询会阻塞等待,直到锁释放,从而读到“已提交”的数据。普通 SELECT 不会被代理,所以全局读已提交需要业务显式用 FOR UPDATE 来换取。

这也意味着:高并发写同一批数据时,全局锁会成为竞争点,极端情况下可能和本地锁形成死锁环。实际落地要控制事务边界、避免长事务。


六、一句话总结

分布式事务解决的是“一次业务跨多个服务/数据库时,如何保证最终一致”。Seata AT 的原理是:TM 发起全局事务并拿到 XID,RM 在各服务里执行本地 SQL 时自动生成 undo log 并向 TC 申请全局锁,一阶段就提交本地事务;二阶段由 TC 根据 TM 的决议驱动 RM 异步提交或按 undo log 反向补偿。核心取舍是用“全局锁 + 读未提交”换低侵入和较高性能,代价是高并发写场景下的锁竞争。

posted @ 2026-10-26 10:35  圆圆ovo  阅读(11)  评论(0)    收藏  举报