适用版本:MySQL 8.0.x
学 InnoDB 时,redo log 和 undo log 是绕不过去的两块核心内容。很多同学第一次接触时会把它们记成两句口诀:
- redo:用于“重做”
- undo:用于“回滚”
这个记忆并没有错,但远远不够。真正进入工程实践后,你会发现:
- 为什么 MySQL 崩溃后能恢复?
- 为什么事务提交了但数据页还没刷盘也不怕?
- 为什么快照读能读到旧版本?
- 为什么长事务会拖垮 purge,导致历史版本堆积?
这些问题都和 redo、undo 直接相关。
这篇文章会从原理、执行过程、典型排障点三个角度,把 redo 与 undo 日志讲清楚。
一、先给出一句总定义
可以先记住下面这组关系:
- redo log:记录“数据页做过什么修改”,用于崩溃恢复与持久性保证
- undo log:记录“修改前是什么样子”,用于事务回滚和 MVCC
两者看起来都和“旧数据、新数据”有关,但职责完全不同:
- redo 面向的是“提交后的恢复”
- undo 面向的是“修改前的可撤销与可见性”
理解这层分工后,很多事务行为就容易串起来了。
二、为什么需要 redo log
1. 直接刷数据页为什么不行
假设你执行一条更新语句:
UPDATE account SET balance = balance - 100 WHERE id = 1;
如果每次事务提交都立刻把被修改的数据页刷到磁盘,会遇到几个问题:
- 随机 I/O 成本太高:数据页分布在不同位置,直接刷页很慢。
- 一条事务可能修改多个页:全部同步落盘,延迟很高。
- 崩溃时写到一半怎么办:只写完部分页会造成状态不一致。
所以 InnoDB 采用的是更高效的方案:
- 先修改内存中的页
- 同时把这次修改对应的 redo 记录下来
- 提交时优先保证 redo 持久化
- 数据页以后再择机刷盘
这就是 WAL(Write-Ahead Logging)。
2. WAL 的核心价值
WAL 的关键思想是:
只要变更日志先安全落盘,数据页之后再刷也没关系。
这样做的收益非常明显:
- 顺序写日志通常比随机写数据页更快
- 提交延迟更低
- 宕机后可以靠 redo 把已提交但未刷盘的数据补回来
所以 redo log 本质上是 InnoDB 事务持久性的基础设施。
三、redo log 记录的到底是什么
很多初学者会误以为 redo log 记录的是一条完整 SQL。实际上不是。
1. redo 不是 SQL 文本
redo log 记录的是 页级物理修改的描述,可以简单理解为:
- 哪个页被改了
- 改了页中的哪个位置
- 改成了什么样
它更接近“存储引擎内部修改记录”,而不是“用户提交的业务语句”。
这也是为什么:
- redo 由 InnoDB 引擎层维护
- binlog 由 Server 层维护
两者服务的目标不同。
2. redo 的组织方式
学习上你可以先把 redo log 理解成:
- 有一块 redo log buffer 在内存中
- 后台把它刷到磁盘上的 redo 文件中
- 每次修改页时,都会生成对应的 redo 记录
这些记录按顺序写入,并通过 LSN(Log Sequence Number) 表示日志推进位置。
LSN 可以理解成 redo 系统中的“进度条”。
四、redo log 如何保证事务提交不丢失
我们用一条更新语句来走一遍大致流程。
1. 执行更新时
当事务执行更新:
BEGIN;
UPDATE account SET balance = balance - 100 WHERE id = 1;
COMMIT;
InnoDB 大致会做这些事:
- 把目标数据页读入 Buffer Pool(如果不在内存)
- 修改内存页
- 生成对应的 redo 记录,写入 redo log buffer
- 生成 undo 记录,留作回滚和 MVCC 使用
2. 提交时
事务执行 COMMIT 时,不一定要求数据页已经刷盘,但通常要求:
- 与该事务相关的 redo 已经按照刷盘策略处理
最典型的强一致配置是:
innodb_flush_log_at_trx_commit = 1
这表示每次提交都把 redo 刷到磁盘文件并执行 fsync。这样即使数据库进程或机器宕机,也能依赖 redo 做恢复。
3. 崩溃恢复时
如果事务已经提交,但脏页还未来得及落盘,实例崩溃重启后:
- InnoDB 会扫描 checkpoint 之后的 redo
- 把尚未反映到数据页上的修改重新应用
这就是“重做”的含义。
五、checkpoint 在 redo 体系中的作用
如果 redo 一直无限追加,那磁盘空间会持续增长,恢复成本也会越来越高。
所以 InnoDB 需要 checkpoint 机制。
1. checkpoint 是什么
可以把 checkpoint 理解为:
告诉系统,某个 LSN 之前的修改,已经安全体现在数据页中了。
有了这个位置后:
- 崩溃恢复不必从很久以前的日志开始
- redo 空间可以循环复用
2. 为什么刷脏页不只是为了“落盘”
很多人以为后台刷脏页只是为了减少宕机损失。实际上还有两个重要原因:
- 推进 checkpoint
- 释放 redo 可用空间
如果刷脏页跟不上,redo 空间压力变大,前台事务也可能受到影响。
这就是为什么写入压力很大时,数据库性能问题常常不只是“磁盘忙”,还会表现为 checkpoint age 紧张、脏页比例高、提交抖动。
六、为什么还需要 undo log
只靠 redo 并不能完成事务系统。
因为 redo 回答的是:“提交后如何恢复”;但数据库还要回答另外两个问题:
- 如果事务还没提交,需要撤销怎么办?
- 如果另一个事务想看到一致性快照,旧版本从哪里来?
这时就需要 undo log。
1. undo 的基本作用
undo log 记录修改前的旧版本信息,主要服务两类场景:
- 事务回滚:把数据恢复成修改前状态
- 一致性读:提供历史版本给快照读使用
2. 一个直观例子
假设原始数据是:
id=1, balance=1000
事务 T1 执行:
UPDATE account SET balance = 900 WHERE id = 1;
这时 InnoDB 会:
- 在数据页中准备写入新值
900 - 在 undo log 中保留旧值相关信息
1000
如果 T1 回滚,就能按 undo 把记录恢复回去。
如果另一个事务 T2 需要看到旧快照,也可能沿着版本链读到旧值。
七、undo log 与 MVCC 的关系
很多人学 MVCC 时会背诵“通过 Read View 和 undo log 实现”,但没有真正建立图景。
1. 行记录不是只有一个版本
在 InnoDB 中,一条记录被更新时,并不是简单地“把旧值覆盖掉然后旧值消失”。
更准确地说:
- 当前记录保存最新版本
- 旧版本通过 undo 链接起来
- 一致性读可以沿着版本链查找“对当前事务可见的那个版本”
2. 快照读为什么不加锁也能读
因为快照读读的是历史可见版本,而不是非要读取“当前最新值”。
例如:
- T1 更新但未提交
- T2 进行普通
SELECT
在可重复读隔离级别下,T2 往往会依据自己的 Read View,结合 undo 版本链,读到一个对自己可见的历史版本,而不是被 T1 阻塞。
3. 这也是长事务危险的原因之一
如果一个长事务一直不结束,它持有的 Read View 会让很多旧版本不能及时清理。
结果就是:
- undo 历史版本堆积
- purge 跟不上
- 表空间膨胀
- 查询链路变长
- 整体性能下降
所以工程上要非常警惕“长时间不提交的事务”。
八、undo log 在回滚时怎么工作
1. 回滚不是“假装没发生”
事务执行失败或手动 ROLLBACK 时,InnoDB 会根据 undo log 做反向操作,把被修改的数据恢复到事务开始前或保存点前的状态。
例如:
BEGIN;
UPDATE product SET stock = stock - 1 WHERE id = 10;
ROLLBACK;
最终数据会回到修改前的值。
2. 回滚也可能很重
回滚不是零成本的。特别是在这些场景下:
- 大事务更新了很多行
- 执行了大批量删除
- 程序异常导致事务超大
这时回滚可能持续很久,甚至造成更多锁等待和系统抖动。
工程上一个很实用的经验是:
大批量改数据时,宁可分批提交,也不要把几百万行修改塞进一个超大事务里。
九、redo 与 undo 在一次更新里如何配合
下面把二者放到同一个事务流程中看。
1. 修改发生时
当一行被更新时:
- InnoDB 先生成 undo,保留旧版本线索
- 再修改内存中的数据页
- 同时生成 redo,记录这次页变更
2. 提交时
提交成功后:
- redo 负责保证“这次修改之后能恢复出来”
- undo 不会立刻消失,因为 MVCC 还可能需要旧版本
3. 后续清理
当系统确认某些旧版本已不再被任何活跃事务需要时,后台 purge 线程才会逐步清理相关 undo 历史。
所以:
- redo 更像“面向未来恢复”
- undo 更像“保留过去版本”
一个负责让已提交事务不丢,一个负责让未提交事务可撤销、已提交事务的旧版本可见。
十、redo 与 undo 的常见混淆点
1. redo 不是回滚日志
redo 的目标不是把修改撤销,而是把已提交修改补做回来。
2. undo 不是持久化提交保证
事务提交后的持久性,核心依赖 redo,而不是 undo。
3. binlog 也不是 redo
binlog 记录的是逻辑层面的变更事件,主要用于复制和恢复;redo 是引擎层的物理日志,主要用于崩溃恢复。
这三者容易混,但一定要分清。
十一、工程实践中最重要的几个参数
1. innodb_flush_log_at_trx_commit
这个参数决定 redo 在提交时的刷盘策略,是最常被问到的参数之一。
常见取值:
1:每次提交都写文件并 fsync,持久性最好2:每次提交写文件,但 fsync 由后台周期执行0:提交时只写 redo buffer,由后台周期写文件并刷盘
工程上的一般建议:
- 金融、订单、核心账务:通常用
1 - 允许极端情况下少量秒级数据丢失的非核心场景:才可能评估
2
不要为了短期 TPS 好看而轻易牺牲持久性。
2. redo 容量相关配置
在 MySQL 8.0 中,redo 空间容量可以通过相关参数统一管理。写入压力较高时,如果 redo 空间过小,容易导致 checkpoint 压力增大、刷盘更激进。
观察点包括:
- 写入高峰是否出现突刺抖动
- 脏页刷盘是否过于频繁
- 恢复时间目标是否可接受
3. undo / purge 相关观测
虽然业务开发通常不直接配置 undo 结构细节,但要学会关注:
- 是否存在长事务
- 历史列表长度是否持续升高
- purge 是否跟不上
这些都与 undo 历史清理直接相关。
十二、几个非常实用的排查视角
1. 为什么数据库提交突然变慢
可以优先怀疑:
- redo 刷盘压力变大
- 磁盘 fsync 延迟上升
- checkpoint 推进困难
- 大量脏页导致后台刷盘过猛
2. 为什么表空间越来越大
除了业务数据本身增长,还要考虑:
- 大量删除后空间未回收
- 长事务导致 undo 相关历史版本迟迟不能清理
- purge 落后
3. 为什么普通查询越来越慢
如果不是索引问题,也可能和 MVCC 历史链变长有关。
典型诱因:
- 长事务长期持有快照
- 高频更新表积累大量旧版本
4. 为什么回滚会持续很久
因为回滚本身也要做工作。超大事务的回滚不仅慢,而且常常会对生产环境造成二次冲击。
十三、常见命令与观测方法
1. 查看 InnoDB 运行状态
SHOW ENGINE INNODB STATUS\G
重点关注:
- 事务列表
- 历史列表长度
- 日志相关信息
- 最近死锁
- Buffer Pool / I/O 状态
2. 查看长事务
SELECT *
FROM information_schema.innodb_trx;
这个视图常用于定位:
- 长时间未提交事务
- 锁等待链路
- 谁在拖慢 purge
3. 查看参数
SHOW VARIABLES LIKE 'innodb_flush_log_at_trx_commit';
SHOW VARIABLES LIKE 'innodb_redo%';
调优之前,先确认当前配置,不要凭经验猜。
十四、一个完整的脑图式理解
可以用下面这组关系来串联:
- 事务修改数据时:生成 undo、修改内存页、记录 redo
- 事务提交时:优先保证 redo 持久化
- 事务回滚时:依据 undo 撤销修改
- 一致性读时:依据 Read View + undo 版本链读取历史版本
- 实例崩溃后:依据 redo 重做已提交但未刷盘的修改
- 后台清理时:purge 逐步删除不再需要的 undo 历史版本
这才是 redo 与 undo 在 InnoDB 中的完整位置。
十五、开发与运维的实战建议
1. 避免长事务
这是最重要的一条。长事务会带来:
- undo 累积
- purge 落后
- 锁持有时间变长
- 主从延迟放大
- 崩溃恢复成本上升
2. 大批量修改要分批提交
不要一次更新几百万行还放在一个事务里。推荐做法:
- 按主键范围分页
- 每批固定数量
- 每批独立提交
- 配套限速和监控
3. 核心业务优先保证 redo 持久性
核心库不要轻易把 innodb_flush_log_at_trx_commit 改成低持久性模式。
4. 关注磁盘延迟而不只看 CPU
很多“数据库突然慢”的根因其实在日志刷盘路径,而不是 SQL 本身。日志设备的稳定性非常关键。
5. 回滚成本要纳入变更方案
做批量变更前,不只要问“执行多久”,还要问:
- 如果中途停止,回滚多久?
- 会不会阻塞线上请求?
- 是否有更安全的分批方案?
十六、小结
redo 与 undo 是 InnoDB 事务系统的两条主线:
- redo 解决的是“提交后怎么保证不丢、宕机后怎么恢复”
- undo 解决的是“未提交怎么回滚、已提交旧版本怎么被快照读看到”
如果只记口诀,你会觉得它们抽象;但一旦把它们放进“更新、提交、回滚、快照读、崩溃恢复”这个完整流程里,就会发现:
它们其实是在回答数据库系统最基本的几个问题——
- 数据怎么安全提交?
- 出故障后怎么恢复?
- 并发下怎么既读得快又读得对?
理解到这里,后面再学 MVCC、binlog、两阶段提交与复制机制,就会自然很多。
📝 版权声明:本文为原创技术博客,转载请注明出处。
如文章中存在错误或不准确之处,欢迎在评论区指正,感谢您的阅读与支持!