Seata 为啥能回滚?扒一扒 undo_log 表里的秘密

0 阅读4分钟

最近有同事问:“Seata 是怎么做到分布式事务回滚的?它又不是数据库,凭什么把已经写进去的数据删掉?”

这个问题问得好。很多人用 Seata,知道加个 @GlobalTransactional 就能跨服务回滚,但对背后原理一知半解。今天我们就拿一条真实的 undo_log 记录,拆开看看 Seata 到底是怎么实现数据回退的。

一、先说结论:Seata 不是靠数据库回滚,而是自己执行“反向操作”

传统本地事务靠数据库的 redo/undo 日志实现回滚。但在分布式场景下,每个服务连接自己的数据库,Seata 作为协调者,并不能直接命令 MySQL “把刚才那条 INSERT 撤销”。

它的做法更直接:在你执行 SQL 的同时,同步记录下“如果要回滚,该怎么把它改回去”。这个“回滚指令”,就存在每个业务库里的 undo_log 表中。

换句话说:Seata 的回滚,是应用层主动执行的一条“反向 SQL”。

二、看一条真实的 undo_log 记录

这段 JSON,就是 Seata 存在 undo_log 表里的一条记录(已格式化):

{
  "xid": "121.41.42.84:8091:427886399291682816",
  "branchId": 427886417402687489,
  "sqlUndoLogs": [
    {
      "sqlType": "INSERT",
      "tableName": "tb_points_mall_order_detail",
      "beforeImage": { /* 空 */ },
      "afterImage": {
        "rows": [
          {
            "fields": [
              {"name":"id", "value":186},
              {"name":"order_id", "value":8},
              {"name":"goods_name", "value":"舒缓新肌焕颜乳"},
              // ... 其他字段
            ]
          }
        ]
      }
    }
  ]
}

重点看这几个部分:

  • sqlType: "INSERT":说明这条 SQL 是插入操作。
  • beforeImage 是空的:因为插入前,这条记录不存在。
  • afterImage 保存了刚插入的整行数据

当全局事务需要回滚时,Seata 的 RM(Resource Manager)模块会读取这条记录,发现是 INSERT,于是生成一条对应的 DELETE 语句:

DELETE FROM tb_points_mall_order_detail WHERE id = 186;

然后执行它——数据就被撤销了。

三、不同 SQL 类型,回滚方式不同

Seata 对三种基本操作分别处理:

操作类型beforeImageafterImage回滚动作
INSERT插入后的完整行DELETE 主键匹配的记录
UPDATE修改前的旧值修改后的新值UPDATE 把字段改回旧值
DELETE删除前的完整行INSERT 把整行重新插回去

举个 UPDATE 的例子:

UPDATE account SET balance = 90 WHERE user_id = 1001;

Seata 会先查出 balance = 100(旧值),存到 beforeImage;执行完 UPDATE 后,把 balance = 90 存到 afterImage

回滚时,就执行:

UPDATE account SET balance = 100 WHERE user_id = 1001;

这其实就是一种自动化的补偿逻辑,也是 Seata AT 模式的核心机制。

四、为什么要有主键?

注意上面所有回滚 SQL 都依赖主键定位记录。这也是为什么 Seata 要求表必须有主键——没有主键,就无法准确识别要回滚的具体行。

如果你的表没主键,Seata 会在事务开始阶段直接报错,不会继续执行。

五、undo_log 表什么时候清理?

  • 事务提交成功:Seata 会在异步阶段自动删除对应的 undo_log 记录(默认延迟 1 秒)。
  • 事务回滚:回滚完成后立即删除。
  • 异常残留:极少数情况下(如服务宕机),可能留下脏数据。Seata 提供了后台清理机制,也可以手动处理。

因此,这张表在正常运行时不会持续增长,无需过度担心存储问题。

六、和 Atomikos 有啥区别?

有人会问:之前用 Atomikos 也能回滚,Seata 有什么不同?

  • Atomikos:基于数据库的 XA 协议,由数据库自身完成两阶段提交和回滚。
  • Seata(AT 模式) :不依赖数据库 XA 支持,而是在应用层拦截 SQL、生成 undo log,通过补偿方式实现回滚。对数据库要求更低(只需有主键),性能也更优。

简单说:Atomikos 依靠数据库的能力完成回滚,Seata 则通过应用层构造反向 SQL 实现回滚。

七、总结

Seata 的回滚能力,源于一套清晰的“快照 + 补偿”机制:

  1. 执行业务 SQL 前,查询并保存“前镜像”;
  2. 执行后,保存“后镜像”;
  3. 将两者打包写入 undo_log
  4. 回滚时,根据操作类型生成并执行反向 SQL。

整个过程对业务代码透明,开发者只需关注业务逻辑,Seata 在底层完成数据一致性保障。

理解了这一点,再看 undo_log 表,就不会觉得神秘——它本质上是一份结构化的变更日志,明确记录了如何撤销一次本地修改。

遇到回滚异常时,查看 undo_log 表中的内容,往往能快速定位问题根源。