引言
作为 InnoDB 存储引擎的核心组件之一,Undo Log(撤销日志) 是事务执行、回滚、多版本并发控制(MVCC)的基石。它与 Redo Log、Binlog 一起构成 MySQL 事务的三大日志体系,分别负责不同方向的保障:Undo 回滚、Redo 崩溃恢复、Binlog 主从复制与归档。
本篇文章从存储结构到工作原理,从事务执行到版本演进,为你彻底讲清 Undo Log 完整机制。
一、InnoDB 表空间:Undo Log 的家在哪里
什么是表空间(Tablespace)
InnoDB 把所有数据、索引、Undo Log 等都放在"表空间"里。表空间本质是一个逻辑存储结构,用于将物理文件组织成可管理的存储单元。
InnoDB 中常用的表空间类型:
| 类型 | 说明 |
|---|---|
| System Tablespace(共享表空间) | 默认在 ibdata1 中,早期版本 Undo Log 全在这里 |
| File-Per-Table Tablespace(独立表空间) | 每个表一个 .ibd 文件 |
| Undo Tablespace(独立 Undo 表空间) | 5.6+,将 Undo Log 从共享空间中分离出来 |
| Temporary Tablespace | 存储临时数据 |
共享表空间(System Tablespace)
结构特点:
-
默认文件:
ibdata1 -
存储内容:
-
Undo Log(旧版)
-
Data Dictionary
-
Doublewrite Buffer
-
Change Buffer
-
部分索引数据
优点
- 统一管理,结构简单
- Undo Log 可以复用,节省管理成本
缺点
- 文件永不缩小(历史性痛点)
- 所有表数据混在一个大文件里,迁移不便
- Undo Log 导致空间膨胀难清理
独立表空间(File-Per-Table)
每个表一个 .ibd 文件,结构更清晰。
优点
- 删除表时空间可回收
- 表迁移、备份更灵活
- 更利于 SSD 的顺序写和扩展
缺点
- Undo Log 仍可能在共享表空间中(5.6 之前)
- 文件数量多,维护成本提升
独立 Undo 表空间(Undo Tablespace) ------ 事务写入的全新归宿
MySQL 5.6 后,Undo Log 移到独立的 undo_001、undo_002 等文件中。
优势:
- Undo 页可以独立回收
- 降低共享表空间膨胀
- Undo 存储更高效
二、Undo Log 的作用:回滚与 MVCC 的基础
Undo Log 是一类 逻辑日志,记录的是数据修改之前的"旧值"。
Undo Log 两大核心能力:
① 事务回滚
示例:
sql
UPDATE account SET balance = balance - 100 WHERE id = 1;
假设原 balance = 500。
Undo Log 会记录:
sql
(undo record)
table: account
id=1
balance = 500
如果事务回滚,用该记录恢复数据即可。
② MVCC(多版本并发控制)实现的关键
MVCC 依赖 Undo Log 与隐藏列:
| 隐藏列 | 含义 |
|---|---|
trx_id |
本行被哪个事务修改 |
roll_pointer |
指向 Undo Log 的指针 |
查询时根据版本链判断行的可见性。
假设对一个行进行了三次修改:
sql
最新版本(data3)
↑ roll_pointer
data2
↑ roll_pointer
data1(最旧)
每个版本存储在 Undo Log 中,每个查询根据事务级别读取不同版本。
三、Undo Log vs Redo Log vs Binlog:三者区别一文看懂
Undo Log 与 Redo Log 区别
| 对比项 | Undo Log | Redo Log |
|---|---|---|
| 作用 | 回滚行记录、MVCC | 数据页崩溃恢复 |
| 记录内容 | 数据修改前的旧值 | 数据修改后的物理变化 |
| 格式 | 逻辑日志 | 物理日志 |
| 写入时机 | 数据修改时生成 | Buffer Pool 脏页产生时生成 |
一句话总结:
Undo 负责撤销,Redo 负责重做。
Redo+Binlog 能否替代 Undo Log?
不能。
原因如下:
① Binlog 只用于主从复制与归档
- Binlog 是逻辑日志
- 不记录行版本
- 无法支持 MVCC
- 无法撤销未提交事务
② Redo Log 不记录旧值
- Redo 记录页物理变化
- 不能回滚数据
- 只能确保数据不丢(崩溃恢复)
因此 Undo Log 是不可替代的。
Binlog 三种格式区别
| 格式 | 优点 | 缺点 |
|---|---|---|
| STATEMENT | 体积小 | 难以重现执行环境 |
| ROW | 精确记录每行变化 | 文件巨大 |
| MIXED | 折中模式 | 实现复杂 |
Binlog 主要用于:
- 主从复制
- PITR 时间点恢复
- 审计与数据归档
四、Undo Log 的物理结构与写入过程
Undo Log 属于 Undo Space 中的段(Segment),包含两类记录:
| 类型 | 说明 |
|---|---|
| Insert Undo Log | Insert 事务提交后即可清除 |
| Update Undo Log | 删除或更新记录的旧版本,用于 MVCC |
Undo Log 写入流程(配合 MVCC)
假设执行:
sql
UPDATE user SET age = 20 WHERE id=1;
流程如下:
-
生成 Undo 记录(旧值)
-
将 Undo 记录写入 Undo Page
-
Data Page 写入新值(Buffer Pool)
-
Redo Log 记录物理变化
-
提交事务后
-
Insert Undo 可立即清理
-
Update Undo 在无事务使用后由 Purge Thread 清除
五、事务回滚机制:如何恢复旧值
回滚操作过程如下:
- 事务遇到
ROLLBACK或发生异常 - 获取 Undo Log 版本链
- 按链表顺序倒序恢复每条记录
- Redo Log 会记录 Undo 操作(保证 crash-safe)
- 事务失败结束
Undo 与 Redo 是双向联动保证一致性的。
六、Undo Log 堆积问题:为什么空间越来越大?
Undo Log 堆积主要源于:
① 长事务
某个事务长时间未提交,旧版本无法被清理。
② Purge Thread 太慢
磁盘 IO 高、系统繁忙导致清理不及时。
③ 大量读取历史版本
MVCC 查询导致版本被引用无法删除。
解决方案
- 关闭长事务
- 确保自动提交
- 优化 Purge
- MySQL 8.0 后 Undo 表空间支持 truncate 回收空间
七、MySQL 各版本 Undo Log 演进
MySQL 5.5 及以前
- Undo 全部在共享表空间
- 无法回收空间
- 容易膨胀
MySQL 5.6
- 引入独立 Undo 表空间
- 支持回收 Undo
MySQL 5.7
- 多 Undo Tablespace
- Undo 自动 purge 更智能
MySQL 8.0
- Undo 支持 truncate
- Undo 重做机制优化
- 根本改善长事务导致空间膨胀的问题
总结
Undo Log 的关键作用:
| 作用 | 说明 |
|---|---|
| 回滚能力 | 让事务具有"撤销"功能 |
| MVCC 实现 | 快照读必须依赖 Undo |
| 事务隔离级别保证 | Repeatable Read 离不开版本链 |
| 逻辑日志 | 记录的是"旧值" |
Undo 在整个 MySQL 体系中不可替代。
Redo 负责让数据不丢,
Binlog 负责让数据能复制,
Undo 负责让事务可回滚、多版本可读取。
三者共同组成了 MySQL 事务引擎的铁三角。