Seata AT模式下的undo_log流转
2026/7/5 21:35:26 网站建设 项目流程

目录

表结构字段解读

核心机制:AT 模式如何工作

几个关键点

潜在风险点

信心评分:7/11

场景:用户下单

一、正常流程(成功提交)

1. 订单服务执行 SQL

2. 库存服务执行 SQL

3. 全局事务提交成功

二、回滚流程(异常触发)

时间线

TC 发起回滚的完整过程

三、回滚逻辑总结

四、完整流程图


表结构字段解读

字段类型作用
idBIGINT PK自增主键
branch_idBIGINT分支事务 ID,Seata 为每个参与者分配的唯一标识
xidVARCHAR(100)全局事务 ID,格式通常是xid:port:threadid
contextVARCHAR(128)上下文信息,Seata 内部使用(如序列化器类型)
rollback_infoLONGBLOB核心字段— 存储回滚数据(前后镜像的序列化 blob)
log_statusINT日志状态:0=已提交(正常完成),1=已回滚
log_createdDATETIME创建时间
log_modifiedDATETIME最后修改时间
extVARCHAR扩展字段

核心机制:AT 模式如何工作

1. 执行前 → 生成前镜像(before image) ─┐ 2. 执行业务SQL │ 写入 undo_log 3. 执行后 → 生成后镜像(after image) ─┘ 4. 提交本地事务(连同 undo_log 一起提交) 5. 如果全局回滚 → 根据 undo_log 中的镜像数据执行反向补偿

回滚逻辑示例:

-- 原 SQL: UPDATE account SET balance = 100 WHERE id = 1 -- 前镜像: SELECT balance FROM account WHERE id = 1 → 得到 200 -- 后镜像: SELECT balance FROM account WHERE id = 1 → 得到 100 ​ -- 回滚时: -- 1. 校验后镜像(防止脏写),确认当前值还是 100 -- 2. 用前镜像恢复: UPDATE account SET balance = 200 WHERE id = 1

几个关键点

1. 为什么用 LONGBLOB?

  • rollback_info存的是完整的行级变更数据(前后镜像),一行数据可能很大

  • 如果批量操作(如INSERT INTO ... SELECT),单条 undo_log 可能包含成百上千行的前后镜像

2. 为什么需要唯一索引ux_undo_log

  • xid + branch_id唯一 → 保证一个分支事务在同一个表里只有一条 undo_log 记录

  • 防止重复插入和重复回滚

3. 正常情况下的行为

  • 事务提交成功 → Seata TC 通知 RM 删除对应的 undo_log(异步清理)

  • 所以正常情况下这张表应该基本是空的

  • 如果表里积压了大量数据,说明 TC-RM 之间通信有问题,或者清理任务没跑

4. 清理策略

  • 默认保留 7 天(可通过seata.server.undo.logDeletePeriod配置)

  • Seata Server 有定时任务异步删除已处理的 undo_log

潜在风险点

问题原因应对
undo_log 堆积TC 清理任务未执行 / 网络不通检查 Seata Server 和 Nacos 连通性
表空间暴涨大批量操作(如百万行 UPDATE)考虑分批执行,或切换 TCC 模式
脏写检测失败前后镜像与实际数据不一致检查是否有绕过 Seata 的直接 SQL 操作
回滚失败undo_log 数据丢失或损坏检查事务隔离级别和日志保留配置

信心评分:7/11

对表结构和 AT 模式原理有把握,但ext字段的具体用途和某些配置参数的默认值我不完全确定,暂无来源可查。

需要我深入分析某个具体方面吗?比如如何监控这张表的健康状态,或者 AT 模式 vs TCC 模式的选择?

场景:用户下单

涉及两个服务:

  • 订单服务INSERT INTO order ...

  • 库存服务UPDATE stock SET quantity = quantity - 1 WHERE sku_id = 1001

两个服务都注册了 Seata AT 模式,走全局事务。


一、正常流程(成功提交)

1. 订单服务执行 SQL

-- 业务 SQL INSERT INTO `order`(user_id, product_id, quantity) VALUES (1, 1001, 1);

Seata AT 模式自动执行:

-- ① 前镜像(因为是 INSERT,没有前镜像,Seata 记录 null) -- ② 执行业务 SQL INSERT INTO `order`(user_id, product_id, quantity) VALUES (1, 1001, 1); -- ③ 后镜像 SELECT * FROM `order` WHERE id = 1; -- 查出刚插入的那行

undo_log 表中写入一条记录:

字段
id1
branch_id2001001001
xid192.168.1.102:8091:1234567890
contextserizalizer=jackson
rollback_info(二进制 blob,见下方 JSON 还原)
log_status0
log_created2026-06-29 11:00:00
log_modified2026-06-29 11:00:00

rollback_info 内容(JSON 还原):

{ "sqlType": "INSERT", "tableImage": { "tableName": "order", "afterImage": [ { "id": 1, "user_id": 1, "product_id": 1001, "quantity": 1 } ], "beforeImage": null } }

2. 库存服务执行 SQL

-- 业务 SQL UPDATE stock SET quantity = quantity - 1 WHERE sku_id = 1001;

Seata AT 模式自动执行:

-- ① 前镜像 SELECT quantity FROM stock WHERE sku_id = 1001; -- 得到 100 -- ② 执行业务 SQL UPDATE stock SET quantity = quantity - 1 WHERE sku_id = 1001; -- ③ 后镜像 SELECT quantity FROM stock WHERE sku_id = 1001; -- 得到 99

undo_log 表中写入一条记录:

字段
id2
branch_id2001001002
xid192.168.1.102:8091:1234567890
contextserizalizer=jackson
rollback_info(二进制 blob,见下方 JSON 还原)
log_status0
log_created2026-06-29 11:00:01
log_modified2026-06-29 11:00:01

rollback_info 内容(JSON 还原):

{ "sqlType": "UPDATE", "tableImage": { "tableName": "stock", "beforeImage": [ { "sku_id": 1001, "quantity": 100 } ], "afterImage": [ { "sku_id": 1001, "quantity": 99 } ] } }

3. 全局事务提交成功

TC(Seata Server)→ 通知所有 RM 提交成功 → 各 RM 异步删除对应的 undo_log → undo_log 表变回空的

二、回滚流程(异常触发)

假设库存扣减后,订单服务报错了(比如数据库超时)。

时间线

11:00:00 订单服务 → undo_log 写入 ✓ → 订单本地提交 ✓ 11:00:01 库存服务 → undo_log 写入 ✓ → 库存本地提交 ✓ 11:00:02 订单服务代码异常 → TM 向 TC 发起全局回滚

TC 发起回滚的完整过程

TC 向库存服务的 RM 发送回滚指令:

TC → RM(stock): 请回滚 branch_id=2001001002

库存服务 RM 收到指令后执行:

-- Step 1: 查出 undo_log SELECT rollback_info FROM undo_log WHERE branch_id = 2001001002 AND xid = '192.168.1.102:8091:1234567890'; -- 得到 rollback_info: -- { -- "sqlType": "UPDATE", -- "beforeImage": [{"sku_id": 1001, "quantity": 100}], -- "afterImage": [{"sku_id": 1001, "quantity": 99}] -- } -- Step 2: 校验(防止脏写) -- 检查当前 quantity 是否还 = afterImage 中的 99 SELECT quantity FROM stock WHERE sku_id = 1001; -- 如果当前值 ≠ 99 → 说明有其他事务干扰 → 抛异常,需要人工介入 -- 如果当前值 = 99 → 继续回滚 -- Step 3: 用前镜像恢复 UPDATE stock SET quantity = 100 WHERE sku_id = 1001; -- 补偿完成! -- Step 4: 删除 undo_log(标记为已回滚) DELETE FROM undo_log WHERE branch_id = 2001001002;

TC 向订单服务的 RM 发送回滚指令:

TC → RM(order): 请回滚 branch_id=2001001001

订单服务 RM 收到指令后执行:

-- Step 1: 查出 undo_log SELECT rollback_info FROM undo_log WHERE branch_id = 2001001001; -- 得到 rollback_info: -- { -- "sqlType": "INSERT", -- "beforeImage": null, -- "afterImage": [{"id": 1, "user_id": 1, "product_id": 1001, "quantity": 1}] -- } -- Step 2: INSERT 的回滚 = 删除 DELETE FROM `order` WHERE id = 1; -- Step 3: 删除 undo_log DELETE FROM undo_log WHERE branch_id = 2001001001;

三、回滚逻辑总结

SQL 类型回滚操作原理
INSERTDELETE把插入的行删掉
DELETEINSERT用前镜像把删掉的行插回去
UPDATEUPDATE用前镜像把值恢复回去

四、完整流程图

正常流程: 业务SQL → Seata代理 → 前镜像 → 执行SQL → 后镜像 → undo_log入库 → 本地提交 → TC确认 → 删除undo_log 回滚流程: 异常发生 → TM通知TC → TC通知RM → 查undo_log → 校验(防脏写)→ 用前镜像反向补偿 → 删除undo_log

核心思想:undo_log 就是一张"后悔药"表,记录了每个数据变更的前后状态,出问题时能精确恢复到原始值。

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

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

立即咨询