redo、undo、binlog 区别:职责、生命周期及两阶段提交(124)

MySQL与关系型数据库约 8 分钟阅读4,561 字符
知识库文档 125 / 251

阅读时间:约 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 生命周期

code
事务开始 → 修改数据 → 生成 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'"。

核心作用:

  1. 事务回滚:将数据恢复到修改前的状态
  2. MVCC:提供历史版本,支持非阻塞读

3.2 存储

  • 存储在 undo 表空间(MySQL 8.0 默认独立 undo 表空间)
  • 默认 2 个 undo 表空间,可配置更多

3.3 生命周期

code
事务开始 → 修改数据 → 生成 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。

核心作用:

  1. 主从复制:从库通过重放 binlog 同步数据
  2. 数据恢复:通过 binlog 恢复到指定时间点

4.2 存储

  • 文件:binlog.000001、binlog.000002...
  • 追加写,单个文件写满后切换新文件
  • 配置:max_binlog_size、expire_logs_days

4.3 三种格式

格式 内容 优点 缺点
STATEMENT SQL 语句 日志小 主从可能不一致
ROW(默认) 行变更 精确 日志大
MIXED 混合 折中 复杂

4.4 生命周期

code
事务开始 → 修改数据 → 生成 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 两阶段提交流程

code
阶段一:Prepare
1. 事务修改数据,写 redo log(状态为 prepare)
2. 写 binlog

阶段二:Commit
3. redo log 状态改为 commit(事务提交完成)

崩溃恢复时:
- redo log 为 prepare 且 binlog 完整 → 提交事务
- redo log 为 prepare 但 binlog 不完整 → 回滚事务

文章配图
文章配图


七、实战:日志配置与监控

7.1 redo log 配置

sql
-- 查看 redo log 大小
SHOW VARIABLES LIKE 'innodb_log_file%';

-- 建议:单文件 1-4GB,总大小 = 文件大小 × 文件数量
-- 大 redo log 减少 Checkpoint 频率,但恢复时间更长

7.2 binlog 配置

sql
-- 查看 binlog 状态
SHOW VARIABLES LIKE 'log_bin%';
SHOW VARIABLES LIKE 'sync_binlog';

-- sync_binlog=1:每次提交都刷盘(最安全,性能最低)
-- sync_binlog=0:由 OS 决定刷盘时机
-- sync_binlog=N:每 N 次提交刷一次盘

7.3 监控

sql
-- 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 的局限:

  1. binlog 记录的是 SQL 语句或行变更,不是页的物理修改。崩溃恢复时需要知道"哪些页被修改了"才能恢复,binlog 不包含这个信息
  2. binlog 是 Statement/Row 格式,需要重新执行 SQL 来恢复,效率远低于 redo log 的直接页恢复
  3. binlog 在 Server 层,不知道 InnoDB 的页结构,无法进行页级别的恢复

如果只有 binlog: 崩溃后需要从最近的全量备份 + binlog 重放恢复,恢复时间可能长达数小时。而 redo log 只需要重放崩溃前几分钟的日志,恢复时间秒级。


Q2: 和 都设为 1,断电后仍丢失已提交事务。可能原因?

参考答案:

可能原因:磁盘写缓存(Write Cache)未关闭。

即使 MySQL 调用了 fsync(),如果磁盘本身有写缓存(带电池的 RAID 卡、SSD 的 DRAM 缓存),fsync() 返回成功不代表数据真正写入持久化介质。

解决方案:

  1. 关闭磁盘写缓存(或使用带电池的 RAID 卡,电池保护写缓存)
  2. 使用支持 FUA(Force Unit Access)的存储设备
  3. 在云环境中,使用 EBS 等持久化块存储

其他可能: 操作系统崩溃时,fsync() 后的数据可能还在文件系统缓存中。sync_binlog=1 只保证 binlog 刷盘,不保证文件系统元数据刷盘。


Q3:undo log 的修改也需要 redo log 保护,为什么?

参考答案:

undo log 本身存储在 undo 页中,undo 页的修改也需要持久化。如果崩溃时 undo 页的修改丢失,将无法回滚未提交的事务。

两层保护:

  1. 数据页修改 → 产生 redo log(保护数据页)
  2. undo 页修改 → 产生 redo log(保护 undo 页)

这不是"无限套娃"—— redo log 保护的是所有物理页的修改,无论这个页是数据页还是 undo 页。redo log 是物理层的保护,它不关心页的内容是数据还是 undo。


Q4:MySQL 8.0 的 redo log 有什么改进?

参考答案:

主要改进:

  1. redo log 归档:MySQL 8.0.17+ 支持 redo log 归档,用于备份时的不完整恢复
  2. redo log 加密:支持 redo log 文件加密
  3. 原子 DDL:MySQL 8.0 的原子 DDL 不再依赖 redo log,而是使用独立的 DDL 日志
  4. innodb_log_writer_threads:MySQL 8.0 支持多个 redo log 写线程,提升高并发写入性能
  5. 改进的 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 与关系型数据库