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 天前
多智能体架构深度解析:企业落地如何选择Skills与SubAgents?
架构·wpf
源之缘-OFD先行者1 天前
自研 WPF 鸟情图表:性能与灵活的双重突破
wpf
Moqiqiuzi1 天前
WPF单实例启动
wpf
Moqiqiuzi1 天前
WPF程序打包成安装包的方法
wpf
码农水水2 天前
国家电网Java面试被问:TCP的BBR拥塞控制算法原理
java·开发语言·网络·分布式·面试·wpf
码农水水2 天前
京东Java面试被问:HTTP/2的多路复用和头部压缩实现
java·开发语言·分布式·http·面试·php·wpf
闻缺陷则喜何志丹2 天前
【C# WPF】TextBox的数据绑定
ui·c#·wpf·mvvm·数据绑定·textbox
码农水水3 天前
得物Java面试被问:大规模数据的分布式排序和聚合
java·开发语言·spring boot·分布式·面试·php·wpf
时光慢煮3 天前
行走在多端之间:基于 Flutter × OpenHarmony 的旅行记录应用实践 —— 旅行详情查看模块解析
flutter·华为·开源·wpf·openharmony
xiaobaishuoAI4 天前
分布式事务实战(Seata 版):解决分布式系统数据一致性问题(含代码教学)
大数据·人工智能·分布式·深度学习·wpf·geo