05.1 - Redo Log 详解
定位:Redo Log 是 InnoDB 保证持久性的核心机制——理解 WAL 和物理日志才能理解"MySQL 不怕断电" 面试高频度:⭐⭐⭐⭐⭐ 考查方式:WAL 原理、为什么比写数据快、Redo Log 刷盘策略、崩溃恢复过程
一、这是什么?为什么需要它?
是什么
Redo Log(重做日志)是 InnoDB 的物理日志,记录"对哪个数据页做了什么修改"。它的核心设计是 WAL(Write-Ahead Logging)——先写日志,再写数据。
为什么需要 Redo Log?
如果没有 Redo Log,一个事务提交的完整过程会是:
事务提交 → 立即把修改的数据页写到磁盘 → 确认写入成功问题是数据页的写入是随机写(16KB 一个页,散落在文件各处),每次提交都写随机位置——性能不可接受。
Redo Log 的解决方案:
事务提交 → 把修改记录以"顺序追加"方式写到 Redo Log → 提交成功 ✅
(后台线程再慢慢把数据页刷到磁盘)比如一个转账操作:
- 没有 Redo Log:每次提交都要写 2 个 16KB 的数据页(32KB 随机 IO)
- 有 Redo Log:每次提交只写几十字节的日志(KB 级顺序 IO)
核心 insight:WAL 的核心思想是"把随机写转成顺序写"——写日志是顺序 IO(快),写数据是随机 IO(慢)。先保证日志安全落盘,数据可以后台异步刷。这样既保证了持久性,又大幅提升了性能。
二、原理拆解
2.1 Redo Log 的物理结构
Redo Log 是固定大小、循环写入的日志文件:
ib_logfile0 ib_logfile1
┌─────────────────────────┐ ┌─────────────────────────┐
│ 已写入 │ 空闲 │ 已写入 │ │ 已写入 │ 空闲 │已写入 │
└─────────┴──────┴────────┘ └─────────┴──────┴────────┘
↑ ↑
write pos (当前写入位置) checkpoint (已刷盘位置)
↑
write pos 超过 checkpoint 时
→ 触发脏页刷盘,推进 checkpoint三个关键位置:
checkpoint ←──── 已刷盘到数据文件的日志位置
│
LSN (Log Sequence Number) → 日志总量(单调递增)
│
write pos ←─── 当前写入位置(Redo Log 文件中的偏移)
│
write pos 不能追上 checkpoint
→ 追上时,强制刷脏页,推进 checkpoint相关配置:
ini
innodb_log_file_size = 512M # 每个 Redo Log 文件大小
innodb_log_files_in_group = 2 # Redo Log 文件数量
innodb_log_buffer_size = 16M # Redo Log Buffer 大小2.2 写入流程
Redo Log 写入流程
UPDATE users SET age=30 WHERE id=1
│
├── 1. 修改 Buffer Pool 中的数据页(内存)
│ └── 该页变为"脏页"
│
├── 2. 生成 Redo Log 记录(物理日志)
│ └── "将 page#42 的 offset#128 处的 age 从 20 改为 30"
│
├── 3. 写入 Redo Log Buffer(内存)
│ └── 每个 DML 操作都写入 Redo Log Buffer
│
├── 4. Redo Log Buffer → Redo Log 文件(磁盘)
│ └── 根据 innodb_flush_log_at_trx_commit 配置
│ ├── 1: 每次事务提交都刷盘(最安全)
│ ├── 2: 每次提交写 OS cache,每秒刷盘
│ └── 0: 每秒刷盘(MySQL 进程崩溃可能丢数据)
│
└── 5. 后台线程异步刷脏页
└── Buffer Pool 中的脏页 → 数据文件2.3 Redo Log 的刷盘策略
innodb_flush_log_at_trx_commit | 提交时行为 | 安全性 | 性能 |
|---|---|---|---|
| 1(默认,推荐) | 每次提交都 fsync Redo Log 到磁盘 | ✅ 不丢数据 | 低(每次提交等待磁盘写) |
| 2 | 每次提交只写到 OS cache,每秒 fsync | ⚠️ OS 崩溃丢 1s 数据 | 中 |
| 0 | 不主动写,Master Thread 每秒 fsync | ⚠️ MySQL 崩溃丢 1s 数据 | 高 |
生产环境推荐:innodb_flush_log_at_trx_commit = 1 + sync_binlog = 1
这是最安全配置,但也是性能最低的。如果业务允许(如日志类应用),可以降级为 2。
2.4 组提交(Group Commit)
为什么需要组提交? 多个事务几乎同时提交时,如果每个事务都单独 fsync,磁盘 IO 无法充分利用。
组提交的优化:将多个事务的 Redo Log 刷盘合并为一次 fsync。
没有组提交:
事务 A: fsync ← 等待磁盘
事务 B: fsync ← 等待磁盘
事务 C: fsync ← 等待磁盘
→ 3 次 fsync,3 次磁盘 IO 延迟
有组提交:
事务 A: 准备提交
事务 B: 准备提交
事务 C: 准备提交
─── 合并 ───
一次 fsync → 三个事务同时完成
→ 1 次 fsync,1 次磁盘 IO 延迟
高并发下:每秒可以提交更多事务!2.5 Checkpoint(检查点)
为什么需要 Checkpoint? Redo Log 是循环写入的,必须确保在覆盖之前,对应的脏页已经刷到磁盘。
Checkpoint 的作用:推进"安全覆盖点"。
没有 Checkpoint:
Redo Log 写满 → 必须等待所有脏页刷完 → 无法继续写入
→ 数据库"卡住"
有 Checkpoint:
后台线程持续刷脏页,推进 checkpoint
只要 checkpoint 跟上 write pos,Redo Log 永远不会满
→ 数据库稳定运行Checkpoint 触发条件:
- Redo Log 文件使用量超过阈值
- 脏页比例超过
innodb_max_dirty_pages_pct - 自适应刷新算法检测到写入压力
三、图解全景
Redo Log 完整流程图
UPDATE 语句
│
▼
┌─────────────────────┐
│ 1. 修改 Buffer Pool │ ← 内存中修改数据页
│ 中的数据页 │
└─────────┬───────────┘
│ 生成 Redo 记录
▼
┌─────────────────────┐
│ 2. Redo Log Buffer │ ← 内存日志缓冲
│ ┌───┬───┬───┐ │
│ │A │B │C │ │ ← 多个事务的日志
│ └───┴───┴───┘ │
└─────────┬───────────┘
│ 组提交、刷盘
▼
┌─────────────────────┐
│ 3. Redo Log 文件 │ ← 磁盘(顺序写)
│ ib_logfile0 │
│ ib_logfile1 │
│ ┌────────────────┐│
│ │A│B│C│... ││ ← WAL: 先写日志
│ └────────────────┘│
└──────────────────────┘
│ 事务提交成功 ✅
│
▼
┌───────────────┐
│ 4. 继续执行 │
│ 用户代码 │ ← 不等待脏页刷盘
└───────┬───────┘
│ 后台异步
▼
┌─────────────────────┐
│ 5. 脏页刷入磁盘 │ ← 磁盘(随机写)
│ /data/mysql/ │
│ *.ibd │
└─────────────────────┘
│
▼
┌─────────────────────┐
│ 6. 推进 Checkpoint │ ← 释放 Redo Log 空间
└─────────────────────┘四、实战验证
查看 Redo Log 配置和状态
sql
-- Redo Log 配置
SHOW VARIABLES LIKE 'innodb_log%';
-- 查看 Redo Log 使用情况
SHOW ENGINE INNODB STATUS\G
-- 查找 LOG 部分:
--
-- LOG
-- ---
-- Log sequence number 26540830 ← 当前 LSN(已写入量)
-- Log flushed up to 26540830 ← 已刷盘 LSN
-- Last checkpoint at 26400000 ← 最近检查点 LSN
-- ...
-- Log i/o's done: 120.00 writes/sec ← 写入速度监控 Redo Log 写入速率
sql
-- 通过 sys 库查看 IO 统计
SELECT * FROM sys.io_global_by_wait_by_bytes
WHERE event_name LIKE '%redolog%';
-- 查看 Redo Log 写入量(MySQL 8.0)
SELECT variable_name, variable_value
FROM performance_schema.global_status
WHERE variable_name LIKE 'Innodb_os_log%';崩溃恢复模拟思路
sql
-- Redo Log 状态值意义
-- Log sequence number (LSN): 当前写入总量
-- Log flushed up to: 已安全写入磁盘的量
-- Last checkpoint at: 已刷盘的脏页对应的 LSN
--
-- 如果 checkpoint 和 flush 相差很大
-- → 说明大量脏页未刷盘
-- → 如果此时崩溃,恢复时需要用 Redo Log 重放
-- → 恢复时间 = 未刷盘数据量 / 重放速度
-- 查看脏页比例
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_pages_dirty';
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_pages_total';
SELECT
(SELECT variable_value FROM performance_schema.global_status
WHERE variable_name = 'Innodb_buffer_pool_pages_dirty') /
(SELECT variable_value FROM performance_schema.global_status
WHERE variable_name = 'Innodb_buffer_pool_pages_total') * 100
AS dirty_page_pct;五、面试视角
| 追问 | 答案要点 |
|---|---|
| Redo Log 的 WAL 原理? | Write-Ahead Logging:先写日志(顺序 IO),再写数据(随机 IO)。保证持久性的同时提升性能 |
| Redo Log 比写数据文件快多少? | 快 10-100 倍。顺序写 vs 随机写,小日志记录 vs 完整 16KB 数据页 |
| 为什么 Redo Log 是物理日志? | 记录"页+偏移+旧值+新值",恢复时可以直接覆盖数据页,不需要重新执行 SQL |
innodb_flush_log_at_trx_commit=1 和 =2 的区别? | 1=每次提交 fsync 磁盘(不丢数据),2=写 OS cache(OS 崩溃可能丢 1s) |
| 什么是组提交? | 合并多个事务的 fsync 为一次,高并发下大幅提升 tps |
| Checkpoint 的作用? | 推进 Redo Log 可覆盖位置,避免日志写满阻塞,同时限制恢复时间 |
| MySQL 崩溃后恢复过程? | 从 checkpoint 开始,正向扫描 Redo Log,重放已提交但未刷盘的修改 |
📚 相关链接
- Undo Log:**Undo Log** — 配合 Redo Log 完成崩溃恢复
- Binlog:**Binlog** — Server 层日志
- 2PC 恢复:**两阶段提交与崩溃恢复**
- ACID 实现:**事务ACID实现**
- ← 返回 **日志系统索引**