undo log在每条insert/update/delete执行过程中、数据页修改前即写入,用于回滚和mvcc;insert undo log提交后立即释放,update/delete undo log由purge线程异步清理。

MySQL undo log 不参与 SQL 执行的“写入路径”,而是在事务开始修改数据时同步生成,核心作用是提供回滚能力与 MVCC 快照读支持。
undo log 什么时候被写入?
不是在 COMMIT 时才写,而是在每条 INSERT/UPDATE/DELETE 执行过程中、数据页被修改前就已写入。InnoDB 会先将旧值(或反向操作信息)记入当前事务关联的 undo log 记录,再更新聚簇索引页。
- insert undo log:只对本事务可见,
COMMIT后可立即释放 - update/delete undo log:需保留至所有依赖该版本的活跃事务结束,由
purge线程异步清理 - 写 undo log 本身也受
redo log保护——崩溃后能靠 redo 恢复 undo 数据,再用 undo 回滚未提交事务
为什么执行 UPDATE 却看不到刚改的旧值?
因为 InnoDB 不直接保存“旧值字段”,而是把整行修改前的完整镜像(或必要字段)存进 undo log,并打上事务 ID 和 rollptr(指向更早版本的指针)。当另一个事务做一致性读(如 SELECT 在 RR 隔离级),InnoDB 就顺着 rollptr 链一路回溯,直到找到该事务可见的版本。
- 这不是复制整行,而是按需构造历史版本;一次
UPDATE可能产生多个 undo log 记录(对应不同快照) - 如果查询需要访问已被 purge 的版本,就会看到
ERROR 1205 (40001): Deadlock found when trying to get lock或空结果(取决于隔离级别和是否启用read_committed_snapshot) - undo log 存储在
undo tablespace(或共享表空间),不走 buffer pool 缓存,读取开销比普通页高
事务回滚时 undo log 真的“撤销磁盘写入”吗?
不。undo log 实现的是逻辑回滚,不是物理还原。
-
INSERT回滚 → 生成一条DELETE操作,删掉刚插入的记录(哪怕它已触发页分裂) -
DELETE回滚 → 生成一条INSERT操作,把原记录插回去(可能插入到新位置) -
UPDATE回滚 → 执行反向UPDATE,把字段设回旧值(不是恢复整个页) - 回滚过程仍会产生新的 redo log,保证回滚操作自身可持久化;undo log 本身不会被删除,只是标记为“已使用”并等待 purge
真正容易被忽略的是:undo log 的生命周期完全脱离用户控制。你无法手动清空某个事务的 undo 记录,也不能强制 purge 正在被 MVCC 引用的版本——哪怕那个事务早已 COMMIT,只要还有长事务没结束,对应 undo 就必须留着。这正是 innodb_max_purge_lag 和 purge_thread 调优的关键所在。











