目录

表结构字段解读

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

几个关键点

潜在风险点

信心评分:7/11

场景:用户下单

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

1. 订单服务执行 SQL

2. 库存服务执行 SQL

3. 全局事务提交成功

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

时间线

TC 发起回滚的完整过程

三、回滚逻辑总结

四、完整流程图


一、undo_log表结构

表结构字段解读

字段 类型 作用
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 数据丢失或损坏 检查事务隔离级别和日志保留配置

场景:用户下单

涉及两个服务:

  • 订单服务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;  -- 得到 99

undo_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 就是一张"后悔药"表,记录了每个数据变更的前后状态,出问题时能精确恢复到原始值。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐