Seata AT 模式:应用层回滚 vs 引擎层回滚

在使用 Seata 的 AT 模式时,观察数据库,会发现了一张名为 undo_log 的表。这张表的存在,往往会让人产生一个误解:Seata 是否直接利用了数据库(如 MySQL InnoDB)底层的 undo log 机制来实现回滚?

如果不能清晰区分这两者的边界,就无法真正理解分布式事务中"两阶段提交"在工程落地时的妥协与设计哲学。

今天我们就从源码和设计原理的角度,彻底拆解 Seata 的 undo log 机制。


Seata AT 模式:应用层回滚 vs 引擎层回滚

直接抛出结论:Seata 的 undo_log 完全是应用层自己生成的"逻辑日志",它与 MySQL 底层的 undo log 没有任何物理上的依赖关系。

虽然名字都叫 "undo",但它们的维度完全不同:

  • MySQL undo log :是引擎层的物理/逻辑日志,用于 MVCC 快照读和未提交事务的原子性回滚。一旦事务 Commit,这部分日志在回滚维度就失效了。
  • Seata undo_log :是业务层的数据快照(Image),用于分布式事务第二阶段的"补偿回滚"。即便本地事务已经 Commit,它依然有效。

一、 数据镜像 (Data Image) 生成机制

Seata AT 模式的核心魔法在于 JDBC 数据源代理 (DataSource Proxy)。它会在你的业务 SQL 执行前后,"偷偷"加塞了几条 SQL,完成了数据快照的记录。

假设你的业务 SQL 如下:

sql 复制代码
UPDATE product SET stock = 90 WHERE id = 1;
-- 假设执行前 stock = 100

Seata 在执行这条 SQL 时,会将其拆解为一个包含 4 个步骤的内部流程。这个过程完全由 Seata 客户端(RM)在内存中完成:

1. 一阶段:生成快照 (Phase 1)

Step 1:前置镜像查询 (Before Image)

在执行业务 SQL 之前,Seata 会解析你的 SQL 语句,自动构造一条查询语句,把"更新前"的数据查出来:

sql 复制代码
SELECT id, stock FROM product WHERE id = 1 FOR UPDATE;
  • 结果id=1, stock=100
  • 动作 :将结果暂存内存,标记为 Before Image

Step 2:执行业务 SQL

执行你写的 UPDATE 语句。此时数据库中的数据已经变成了 90。

Step 3:后置镜像查询 (After Image)

业务 SQL 执行完,但本地事务提交之前,Seata 再次查询:

sql 复制代码
SELECT id, stock FROM product WHERE id = 1;
  • 结果id=1, stock=90
  • 动作 :将结果暂存内存,标记为 After Image

Step 4:插入回滚日志

Seata 将 Before Image 和 After Image 打包成一个 JSON/Blob 格式的对象,作为一条记录插入到业务库的 undo_log 表中。

sql 复制代码
INSERT INTO undo_log (branch_id, xid, rollback_info, ...) 
VALUES (..., '{"beforeImage":{100}, "afterImage":{90}}', ...);

Step 5:本地事务提交

注意,此时 MySQL 的本地事务已经 Commit 了! 锁已经被释放,MySQL 认为这笔交易结束了。

下面是这个过程的时序图:

代码段
MySQL Database Seata JDBC Proxy 业务代码 MySQL Database Seata JDBC Proxy 业务代码 开启本地事务 执行 UPDATE product SET stock=90... SELECT ... FOR UPDATE (Before Image) stock = 100 执行业务 UPDATE SELECT ... (After Image) stock = 90 INSERT INTO undo_log ... COMMIT (提交本地事务) 执行成功

二、 回滚机制:反向补偿 (Compensation)

明白了生成机制,回滚机制就很容易理解了。

既然本地事务已经 Commit,MySQL 原生的 undo log 就无法使用了(因为 MySQL 不支持回滚已提交的事务)。当 TC(事务协调器)决议全局回滚时,Seata 执行的是应用层的补偿操作

二阶段回滚 (Phase 2 Rollback) 流程:

  1. 查找日志 :RM 根据 XID 和 Branch ID 去 undo_log 表里找到对应的记录。

  2. 数据校验 :校验 After Image 与当前数据库的数据是否一致(为了防止脏写,如果中间有人改过数据,回滚会失败并报警)。

  3. 生成反向 SQL :根据 Before Image (stock=100),自动生成还原数据的 SQL:

    sql 复制代码
    UPDATE product SET stock = 100 WHERE id = 1;
  4. 执行还原:执行这条反向 SQL,将数据改回原样。

  5. 清理日志 :删除 undo_log 中的记录。

三、 深度对比:Seata undo_log vs MySQL undo log

为了彻底厘清区别,我整理了如下对比表:

特性 Seata undo_log MySQL undo log
角色 分布式事务的"后悔药" 本地事务的原子性保证
生成者 Java 应用层 (Seata Client) 数据库引擎层 (InnoDB)
存储位置 业务库中的一张普通表 (undo_log) 系统表空间或专门的 Undo Tablespace
内容格式 逻辑数据 (JSON),包含前后镜像 物理/逻辑混合日志,二进制格式
生命周期 事务提交后依然存在 (直到 Phase 2 完成) 事务提交后即失去回滚作用 (仅用于 MVCC)
回滚原理 执行 反向 SQL (补偿) 从日志中恢复旧版本页

四、 架构思考

为什么 Seata 不直接利用数据库的 XA 协议或 undo log,而要自己搞一套 undo_log 表?

这是为了解决 长事务带来的锁竞争问题

  • 传统 XA 模式:在全局事务未完成前,必须一直持有数据库的锁。如果网络波动导致全局事务耗时 2 秒,那这行数据就被锁了 2 秒,并发性能极差。
  • Seata AT 模式 :通过一阶段本地提交 ,快速释放了数据库层面的锁。这让数据库的并发能力不再受限于分布式事务的耗时。而 undo_log 表,就是为了换取这种高性能而付出的存储成本。

Seata 的 "AT" 全称是 Automatic Transaction,它的本质就是:Seata 帮你把 TCC (Try-Confirm-Cancel) 模式中的 Cancel 阶段代码给自动生成了。

理解了这一点,你也就理解了 Seata AT 模式的设计精髓。

相关推荐
@淡 定1 天前
分布式事务解决方案
分布式·wpf
棉晗榜1 天前
WPF将程序集里面嵌入的资源文件下载到本机磁盘中,将项目中的文件下载到桌面
开发语言·wpf
△曉風殘月〆1 天前
WPF MVVM实战系列教程(一、Prism框架介绍)
wpf·mvvm·prism
Aevget1 天前
DevExpress WPF中文教程:Data Grid - 如何绑定到有限制的自定义服务(三)?
wpf·界面控件·devexpress·ui开发·.net 10
△曉風殘月〆2 天前
WPF MVVM实战系列教程(二、使用Visual Studio 创建Prism项目)
wpf·mvvm·prism
bugcome_com4 天前
WPF 核心布局控件全解析:从 Grid 到 UniformGrid 的实战应用
c#·wpf
观无4 天前
WPF-Datagrid控件的无缝滚动
wpf
꧁༺℘₨风、凌๓༻꧂4 天前
C# WPF 项目中集成 Pdf查看器
pdf·c#·wpf