目录
表结构字段解读
核心机制:AT 模式如何工作
几个关键点
潜在风险点
信心评分:7/11
场景:用户下单
一、正常流程(成功提交)
1. 订单服务执行 SQL
2. 库存服务执行 SQL
3. 全局事务提交成功
二、回滚流程(异常触发)
时间线
TC 发起回滚的完整过程
三、回滚逻辑总结
四、完整流程图
表结构字段解读
| 字段 | 类型 | 作用 |
|---|---|---|
id | BIGINT PK | 自增主键 |
branch_id | BIGINT | 分支事务 ID,Seata 为每个参与者分配的唯一标识 |
xid | VARCHAR(100) | 全局事务 ID,格式通常是xid:port:threadid |
context | VARCHAR(128) | 上下文信息,Seata 内部使用(如序列化器类型) |
rollback_info | LONGBLOB | 核心字段— 存储回滚数据(前后镜像的序列化 blob) |
log_status | INT | 日志状态:0=已提交(正常完成),1=已回滚 |
log_created | DATETIME | 创建时间 |
log_modified | DATETIME | 最后修改时间 |
ext | VARCHAR | 扩展字段 |
核心机制: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 表中写入一条记录:
| 字段 | 值 |
|---|---|
| id | 1 |
| branch_id | 2001001001 |
| xid | 192.168.1.102:8091:1234567890 |
| context | serizalizer=jackson |
| rollback_info | (二进制 blob,见下方 JSON 还原) |
| log_status | 0 |
| log_created | 2026-06-29 11:00:00 |
| log_modified | 2026-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; -- 得到 99undo_log 表中写入一条记录:
| 字段 | 值 |
|---|---|
| id | 2 |
| branch_id | 2001001002 |
| xid | 192.168.1.102:8091:1234567890 |
| context | serizalizer=jackson |
| rollback_info | (二进制 blob,见下方 JSON 还原) |
| log_status | 0 |
| log_created | 2026-06-29 11:00:01 |
| log_modified | 2026-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 类型 | 回滚操作 | 原理 |
|---|---|---|
| INSERT | DELETE | 把插入的行删掉 |
| DELETE | INSERT | 用前镜像把删掉的行插回去 |
| UPDATE | UPDATE | 用前镜像把值恢复回去 |
四、完整流程图
正常流程: 业务SQL → Seata代理 → 前镜像 → 执行SQL → 后镜像 → undo_log入库 → 本地提交 → TC确认 → 删除undo_log 回滚流程: 异常发生 → TM通知TC → TC通知RM → 查undo_log → 校验(防脏写)→ 用前镜像反向补偿 → 删除undo_log
核心思想:undo_log 就是一张"后悔药"表,记录了每个数据变更的前后状态,出问题时能精确恢复到原始值。