阅读时间:约 45 分钟 | 难度:中高级 | 编号:124
一、引言:MySQL 的三种日志,各司其职
MySQL InnoDB 有三种核心日志:redo log、undo log 和 binlog。它们职责不同、生命周期不同、存储位置不同,但协同工作保证了事务的 ACID 和主从复制的一致性。
一句话区分:
- redo log:崩溃恢复,保证持久性(InnoDB 层)
- undo log:事务回滚 + MVCC,保证原子性(InnoDB 层)
- binlog:主从复制 + 数据恢复,保证可恢复性(Server 层)

二、redo log:物理日志,循环写
2.1 职责
redo log 记录的是"数据页的物理修改"。例如:"将表空间 0 的第 100 号页的偏移量 200 处的 4 字节改为 0x12345678"。
核心作用: 崩溃恢复。MySQL 崩溃后,通过重放 redo log 恢复未刷盘的数据。
2.2 存储
- 文件:
ib_logfile0、ib_logfile1 - 大小固定,循环写入
- 配置:
innodb_log_file_size+innodb_log_files_in_group
2.3 生命周期
事务开始 → 修改数据 → 生成 redo log → 写入 redo log buffer
→ 事务提交 → redo log 刷盘(innodb_flush_log_at_trx_commit=1)
→ Checkpoint 推进 → redo log 空间可被覆盖
2.4 关键特性
- 物理日志,记录页的修改
- 循环写,空间固定
- 顺序写,性能极高
- InnoDB 引擎层

三、undo log:逻辑日志,版本链
3.1 职责
undo log 记录的是"修改前的逻辑值"。例如:"UPDATE 语句将 name 从 'Alice' 改为 'Bob',undo log 记录 name='Alice'"。
核心作用:
- 事务回滚:将数据恢复到修改前的状态
- MVCC:提供历史版本,支持非阻塞读
3.2 存储
- 存储在 undo 表空间(MySQL 8.0 默认独立 undo 表空间)
- 默认 2 个 undo 表空间,可配置更多
3.3 生命周期
事务开始 → 修改数据 → 生成 undo log(记录旧值)
→ 事务提交 → undo log 不再需要(但 MVCC 可能还需要)
→ Purge 线程清理 → undo log 空间回收
3.4 关键特性
- 逻辑日志,记录修改前的逻辑值
- INSERT 的 undo:记录主键(回滚时 DELETE)
- UPDATE 的 undo:记录旧值(回滚时恢复)
- DELETE 的 undo:记录整行(回滚时 INSERT)
- InnoDB 引擎层

四、binlog:逻辑日志,追加写
4.1 职责
binlog 记录的是"SQL 语句的逻辑操作"。例如:UPDATE t SET name='Bob' WHERE id=1。
核心作用:
- 主从复制:从库通过重放 binlog 同步数据
- 数据恢复:通过 binlog 恢复到指定时间点
4.2 存储
- 文件:
binlog.000001、binlog.000002... - 追加写,单个文件写满后切换新文件
- 配置:
max_binlog_size、expire_logs_days
4.3 三种格式
| 格式 | 内容 | 优点 | 缺点 |
|---|---|---|---|
| STATEMENT | SQL 语句 | 日志小 | 主从可能不一致 |
| ROW(默认) | 行变更 | 精确 | 日志大 |
| MIXED | 混合 | 折中 | 复杂 |
4.4 生命周期
事务开始 → 修改数据 → 生成 binlog → 写入 binlog cache
→ 事务提交 → binlog 刷盘(sync_binlog=1)
→ 超过 expire_logs_days → 自动清理
4.5 关键特性
- 逻辑日志,记录 SQL 操作
- 追加写,无限增长(需定期清理)
- 所有存储引擎共用
- MySQL Server 层

五、三种日志对比
| 维度 | redo log | undo log | binlog |
|---|---|---|---|
| 层级 | InnoDB 引擎层 | InnoDB 引擎层 | MySQL Server 层 |
| 内容 | 物理日志(页修改) | 逻辑日志(旧值) | 逻辑日志(SQL/行变更) |
| 写入方式 | 循环写 | 随机写 | 追加写 |
| 作用 | 崩溃恢复 | 回滚 + MVCC | 主从复制 + 恢复 |
| 生命周期 | Checkpoint 后可覆盖 | Purge 后可回收 | 过期后清理 |
| 事务提交 | 必须先刷盘 | 不需要 | 可选刷盘 |
| 大小 | 固定 | 增长 | 增长 |

六、两阶段提交:redo log 与 binlog 的协调
6.1 为什么需要两阶段提交?
redo log 和 binlog 分属不同层,需要保证两者的一致性:事务提交后,redo log 和 binlog 要么都写入,要么都不写入。
如果先写 redo log 再写 binlog:
- redo log 写成功,binlog 写失败,崩溃恢复 → 主库有数据,从库没有
如果先写 binlog 再写 redo log:
- binlog 写成功,redo log 写失败,崩溃恢复 → 从库有数据,主库没有
6.2 两阶段提交流程
阶段一:Prepare
1. 事务修改数据,写 redo log(状态为 prepare)
2. 写 binlog
阶段二:Commit
3. redo log 状态改为 commit(事务提交完成)
崩溃恢复时:
- redo log 为 prepare 且 binlog 完整 → 提交事务
- redo log 为 prepare 但 binlog 不完整 → 回滚事务

七、实战:日志配置与监控
7.1 redo log 配置
-- 查看 redo log 大小
SHOW VARIABLES LIKE 'innodb_log_file%';
-- 建议:单文件 1-4GB,总大小 = 文件大小 × 文件数量
-- 大 redo log 减少 Checkpoint 频率,但恢复时间更长
7.2 binlog 配置
-- 查看 binlog 状态
SHOW VARIABLES LIKE 'log_bin%';
SHOW VARIABLES LIKE 'sync_binlog';
-- sync_binlog=1:每次提交都刷盘(最安全,性能最低)
-- sync_binlog=0:由 OS 决定刷盘时机
-- sync_binlog=N:每 N 次提交刷一次盘
7.3 监控
-- redo log 使用情况
SHOW STATUS LIKE 'Innodb_log_waits'; -- >0 说明 redo log 写满过
-- binlog 使用情况
SHOW BINARY LOGS;
-- undo log 使用情况
SELECT * FROM information_schema.innodb_metrics
WHERE name LIKE '%undo%';

八、总结
| 场景 | 依赖的日志 |
|---|---|
| 事务回滚 | undo log |
| 崩溃恢复 | redo log |
| MVCC 快照读 | undo log |
| 主从复制 | binlog |
| 数据恢复到时间点 | binlog + 全量备份 |
| 两阶段提交 | redo log + binlog |

九、面试官视角提问
Q1:为什么 binlog 不能替代 redo log?如果 MySQL 只有 binlog,崩溃恢复会有什么问题?
Q2:**sync_binlog=1**和**innodb_flush_log_at_trx_commit=1**都设为 1,但服务器断电后仍然丢失了已提交的事务。可能的原因是什么?
Q3:undo log 也需要写 redo log 来保护,这听起来像是"无限套娃"。为什么 undo log 的修改也需要 redo log?
Q4:MySQL 8.0 的 redo log 有什么改进?和 MySQL 5.7 有什么不同?
Q5:binlog 的 ROW 格式下,一条 UPDATE 语句修改了 1000 行,binlog 会记录多少内容?STATEMENT 格式呢?
Q6:两阶段提交中,如果 redo log prepare 后、binlog 写入前崩溃,恢复时如何处理?binlog 写入后、redo log commit 前崩溃呢?
作者注:本文为系列第 124 篇,属于「08 MySQL 与关系型数据库」分类。
十、面试官提问 · 参考答案
Q1:为什么 binlog 不能替代 redo log?只有 binlog 会有什么问题?
参考答案:
binlog 不能替代 redo log 的核心原因:binlog 是逻辑日志,redo log 是物理日志。
binlog 的局限:
- binlog 记录的是 SQL 语句或行变更,不是页的物理修改。崩溃恢复时需要知道"哪些页被修改了"才能恢复,binlog 不包含这个信息
- binlog 是 Statement/Row 格式,需要重新执行 SQL 来恢复,效率远低于 redo log 的直接页恢复
- binlog 在 Server 层,不知道 InnoDB 的页结构,无法进行页级别的恢复
如果只有 binlog: 崩溃后需要从最近的全量备份 + binlog 重放恢复,恢复时间可能长达数小时。而 redo log 只需要重放崩溃前几分钟的日志,恢复时间秒级。
Q2: 和 都设为 1,断电后仍丢失已提交事务。可能原因?
参考答案:
可能原因:磁盘写缓存(Write Cache)未关闭。
即使 MySQL 调用了 fsync(),如果磁盘本身有写缓存(带电池的 RAID 卡、SSD 的 DRAM 缓存),fsync() 返回成功不代表数据真正写入持久化介质。
解决方案:
- 关闭磁盘写缓存(或使用带电池的 RAID 卡,电池保护写缓存)
- 使用支持
FUA(Force Unit Access)的存储设备 - 在云环境中,使用 EBS 等持久化块存储
其他可能: 操作系统崩溃时,fsync() 后的数据可能还在文件系统缓存中。sync_binlog=1 只保证 binlog 刷盘,不保证文件系统元数据刷盘。
Q3:undo log 的修改也需要 redo log 保护,为什么?
参考答案:
undo log 本身存储在 undo 页中,undo 页的修改也需要持久化。如果崩溃时 undo 页的修改丢失,将无法回滚未提交的事务。
两层保护:
- 数据页修改 → 产生 redo log(保护数据页)
- undo 页修改 → 产生 redo log(保护 undo 页)
这不是"无限套娃"—— redo log 保护的是所有物理页的修改,无论这个页是数据页还是 undo 页。redo log 是物理层的保护,它不关心页的内容是数据还是 undo。
Q4:MySQL 8.0 的 redo log 有什么改进?
参考答案:
主要改进:
- redo log 归档:MySQL 8.0.17+ 支持 redo log 归档,用于备份时的不完整恢复
- redo log 加密:支持 redo log 文件加密
- 原子 DDL:MySQL 8.0 的原子 DDL 不再依赖 redo log,而是使用独立的 DDL 日志
- innodb_log_writer_threads:MySQL 8.0 支持多个 redo log 写线程,提升高并发写入性能
- 改进的 Checkpoint:更高效的 Checkpoint 算法,减少性能抖动
Q5:binlog ROW 格式下,一条 UPDATE 修改了 1000 行,binlog 记录多少内容?
参考答案:
ROW 格式:记录 1000 行的变更信息。 每行记录修改前的值和修改后的值(binlog_row_image=FULL)或只记录修改的列(binlog_row_image=MINIMAL)。
STATEMENT 格式:记录一条 UPDATE 语句。 但如果语句包含 NOW()、RAND() 等不确定函数,主从结果可能不一致。
ROW 格式的binlog大小约为: 1000 × (修改前列 + 修改后列) × 每列大小。如果每行 500 字节,修改了 100 字节,binlog 约 1000 × 200 = 200KB。
Q6:两阶段提交中,redo log prepare 后 binlog 写入前崩溃 vs binlog 写入后 commit 前崩溃?
参考答案:
redo log prepare 后,binlog 写入前崩溃:
- 恢复时发现 redo log 为 prepare 状态,但 binlog 不完整(没有对应事务的 binlog)
- 回滚事务,主库和从库都没有该事务的数据
binlog 写入后,redo log commit 前崩溃:
- 恢复时发现 redo log 为 prepare 状态,且 binlog 完整(有对应事务的 binlog)
- 提交事务,确保主库和从库都有该事务的数据
- 因为 binlog 已写入,从库已经或将要重放该事务
这就是两阶段提交的精髓: 以 binlog 是否完整作为决策依据,保证主从一致性。
全文完 | 编号 124 | 分类 08 MySQL 与关系型数据库