undo log 不只是“回滚日志”。回滚需要它,MVCC 的历史版本也需要它;记录上的 roll pointer、事务 id 和 undo 版本链一起,决定了一个事务能看到哪一版数据。

这也是 undo 最容易被低估的地方:它让快照读成为可能,也会被长事务拖成系统债务。读懂 undo,才能理解回滚、快照读、历史版本清理和长事务治理为什么是一组问题。

先把机制边界说清楚

undo log 记录的是数据被修改前的旧值或反向操作。事务回滚时,InnoDB 通过 undo 把记录恢复到修改前;快照读时,InnoDB 通过记录上的 roll pointer 沿 undo 版本链找到对当前事务可见的历史版本。

整体路径

undo log:回滚 + MVCC 都靠它

上面这张图先看入口和边界:宏观上,更新一行记录时,InnoDB 会先把旧版本写入 undo,再修改聚簇索引记录中的事务 id 和回滚指针。事务提交后,undo 不会立刻删除,因为可能还有旧 ReadView 需要它。后台 purge 线程会在确认没有事务需要旧版本后清理 undo。

undo log 三种操作的内部结构

第二张图再看结构关系。

底层流程

MVCC 怎么靠 undo log 读历史版本

底层拆解先看数据结构。「undo log」至少涉及下面几类结构:

  • undo record:保存旧值或反向操作信息。
  • rollback segment:组织 undo 页和事务回滚信息。
  • trx_id:记录最后修改该行的事务编号。
  • roll_pointer:从聚簇索引记录指向 undo 版本链。
  • history list:等待 purge 清理的历史版本集合。

再看完整执行流程:

  1. 事务更新记录前生成 undo record。
  2. 聚簇索引记录写入新的 trx_id 和 roll_pointer。
  3. 事务回滚时沿 undo 执行反向修改。
  4. 快照读发现当前版本不可见时,沿 undo 链查找旧版本。
  5. 所有相关 ReadView 结束后,purge 清理历史 undo。

取舍与边界

版本差异上,MySQL 8.0 对 undo 表空间管理、truncate 和监控能力比早期版本更成熟。5.7 也有独立 undo 表空间配置,但治理体验弱一些。无论版本如何,长事务拖住 purge 的本质不会改变。

undo 的短板是它会被长事务拖成系统性债务。一个长时间不提交的快照读,可能让大量历史版本无法清理;更新越频繁,undo 链越长,purge 压力、空间占用和可见性判断成本都会上升。

典型问题:用机制化例子排查

长事务问题可以用报表查询来理解:一个长时间不结束的快照读,会让系统保留更早的历史版本,写入越多,undo 清理压力越大。

可以落到这些动作:

  • 监控 history list length、长事务和 undo 表空间增长。
  • 报表和导出任务避免在主库开启长快照事务。
  • 业务代码不要把事务包到远程调用或人工操作之外。
  • 大量更新任务要拆批提交,给 purge 留出持续清理窗口。

收束:历史版本也有成本

undo log 让回滚和快照读成为可能,但它不是免费的历史档案。事务越长,系统越要为过去保留更多版本;长事务治理,本质上也是 undo 成本治理。


关于十三Tech

我是十三,All in AI Agent 方向的架构师,专注 AI 工程实践。

我相信 AI 是程序员的最佳搭档,也希望帮助每一位开发者更好地驾驭 AI。

如果你想继续跟完这套「图解 MySQL」,欢迎关注公众号 「十三Tech」。后续会继续按机制、图解和实战排查这条线更新。

十三Tech公众号二维码