在现代关系型数据库系统中,事务的 持久性(Durability)是 ACID 特性的关键一环。为了在系统崩溃后仍能恢复数据一致性,InnoDB 引擎引入了 REDO LOG(重做日志)机制。

本文将深入剖析 REDO LOG 的作用、设计思想、组织结构、写入流程以及清理策略,帮助读者全面理解其在 InnoDB 中的核心地位。


🎯 目录

  1. 为什么需要 REDO LOG?
  2. REDO LOG 的设计目标与 Physiological Logging
  3. REDO LOG 的内容分类
  4. REDO LOG 的三层组织结构
  5. 高效写入机制(MySQL 8.0 优化)
  6. REDO LOG 的安全清理:Checkpoint 机制
  7. 总结
  8. 延伸阅读

1️⃣ 为什么需要 REDO LOG?

InnoDB 采用 Buffer Pool 缓存数据页以提升性能,对磁盘的修改往往滞后于内存。若此时发生崩溃,未刷盘的脏页将丢失,破坏数据一致性。

🔄 WAL 机制

为解决此问题,InnoDB 引入 WAL(Write-Ahead Logging)机制:

┌─────────────────────────────────────────────────┐
│  在修改 Page 前,先将变更记录写入 REDO LOG      │
│  并确保 REDO 先于 Page 落盘                      │
└─────────────────────────────────────────────────┘

系统重启时,通过重放 REDO LOG,可将 Page 恢复至崩溃前状态,从而保证事务的持久性。


2️⃣ REDO LOG 的设计目标与 Physiological Logging

理想的 REDO LOG 需满足以下要求:

设计目标 说明
数据量小 减少 I/O 开销,提升吞吐
幂等性 重放多次结果一致,避免重复应用
基于 Page 便于并行恢复

📊 传统日志对比

日志类型 优点 缺点
Logical Logging 体积小 恢复慢
Physical Logging 恢复快 日志量大

💡 InnoDB 的解决方案:Physiological Logging

InnoDB 采用 Physiological Logging —— 以 Page 为单位,在 Page 内部使用逻辑方式记录变更

示例:MLOG_REC_UPDATE_IN_PLACE
(Page ID, Record Offset, (Field 1, Value 1), ..., (Field i, Value i))

这种方式兼顾了日志体积与恢复效率,但也带来两个挑战:

⚠️ 挑战一:Page 状态必须正确

由于 InnoDB Page(默认 16KB)大于文件系统原子写单位(4KB),可能出现"半写页"。

解决方案:InnoDB 引入 Double Write Buffer 机制,确保总能找到一个完整有效的 Page。

⚠️ 挑战二:保证重放幂等

InnoDB 为每条 REDO 分配全局唯一递增的 LSN(Log Sequence Number)。

机制

  • Page 在修改时会记录对应 REDO 的 LSN(存于 FIL_PAGE_LSN 字段)
  • 恢复时,若 Page 的 LSN ≥ REDO 的 LSN,则跳过该 REDO,实现幂等重放

3️⃣ REDO LOG 的内容分类

MySQL 8.0 中定义了 65 种 REDO 类型,可分为三大类:

📁 (1)作用于 Page 的 REDO(占绝大多数)

子类型 说明 示例
Index Page 索引页操作 MLOG_REC_INSERT, MLOG_REC_UPDATE_IN_PLACE, MLOG_REC_DELETE
Undo Page Undo 页操作 -
R-tree Page R树页操作 -
示例:MLOG_REC_UPDATE_IN_PLACE 结构
┌─────────────────────────────────────────────────────────┐
│ Header (固定)                                            │
├─────────────────────────────────────────────────────────┤
│ Type           │ Space ID       │ Page Number            │
├─────────────────────────────────────────────────────────┤
│ Body (可变)                                              │
├─────────────────────────────────────────────────────────┤
│ Record Offset  │ Update Field Count │ (Field Number,    │
│                │                    │  Data Length,     │
│                │                    │  Data) × N        │
└─────────────────────────────────────────────────────────┘

📂 (2)作用于 Space 的 REDO

用于文件级操作:

类型 说明
MLOG_FILE_CREATE 文件创建
MLOG_FILE_DELETE 文件删除
MLOG_FILE_RENAME 文件重命名

特点

  • 此类 REDO 在操作成功后才记录
  • 恢复时主要用于校验文件状态
  • Page Number 固定为 0

🏷️ (3)Logic 类型 REDO

不修改数据,仅提供控制信息。

类型 说明
MLOG_MULTI_REC_END 标识一个原子操作(mtr)的结束

4️⃣ REDO LOG 的三层组织结构

REDO LOG 从逻辑到物理分为三层:

📐 (1)逻辑层(Logical REDO)

  • 由连续的 REDO 记录组成
  • 使用全局递增偏移 sn(sequence number)标识位置
  • log_sys.sn 维护当前最大 sn

📦 (2)物理层(Log Block)

  • 磁盘以 Block 为单位读写(512B)
  • 每个 Block 结构:
┌──────────────────────────────────────────────────────┐
│ Block Header (12 bytes)                              │
├──────────────┬──────────────┬──────────────┬─────────┤
│ Flush Flag + │ Data Length  │ First Record │ Check-  │
│ Block Num    │ (2B)         │ Offset (2B)  │ point   │
│ (4B)         │              │              │ Num (4B)│
├──────────────────────────────────────────────────────┤
│ Data Area (498 bytes) - Logical REDO                 │
├──────────────────────────────────────────────────────┤
│ Block Trailer (4 bytes) - Checksum                   │
└──────────────────────────────────────────────────────┘

跨 Block 存储:由于 REDO 长度不定,可能跨 Block 存储。

🔢 LSN 与 sn 的换算
constexpr inline lsn_t log_translate_sn_to_lsn(lsn_t sn) {
  return (sn / LOG_BLOCK_DATA_SIZE * OS_FILE_LOG_BLOCK_SIZE +
          sn % LOG_BLOCK_DATA_SIZE + LOG_BLOCK_HDR_SIZE);
}

📄 (3)文件层(Redo Files)

  • 文件命名ib_logfile0, ib_logfile1, …
  • 循环使用,由 innodb_log_files_in_group 控制数量
  • 文件结构
┌─────────────────────────────────────────────────────┐
│ Block 0: Header Block                                │
│  - Format (4B)                                      │
│  - Start LSN (8B)                                   │
│  - Creator (32B)                                    │
├─────────────────────────────────────────────────────┤
│ Block 1-3: Checkpoint Blocks (ib_logfile0 only)     │
├─────────────────────────────────────────────────────┤
│ Block 4+: Log Data Blocks                           │
└─────────────────────────────────────────────────────┘

LSN 到文件 offset 的映射

const auto real_offset = log.current_file_real_offset + (lsn - log.current_file_lsn);

5️⃣ 高效写入机制(MySQL 8.0 优化)

REDO 写入是事务提交的关键路径。MySQL 8.0 引入 无锁并发写入 架构:

🔄 写入流程图

REDO 生成

Log Buffer

Page Cache

磁盘

唤醒用户线程

📝 详细步骤

(1)REDO 生成:mtr(mini-transaction)
  • 原子操作的所有 REDO 先写入 mtr 的本地 m_log
  • mtr_commit() 时批量拷贝到 Log Buffer
(2)写入 Log Buffer:无锁分配
/* Reserve space in sequence of data bytes: */
const sn_t start_sn = log.sn.fetch_add(len);
  • 多个 mtr 并发调用 log_buffer_reserve()
  • 通过 fetch_add 原子获取独享空间,避免锁竞争
(3)写入 Page Cache:log_writer + link_buf

link_buf 工作原理

┌─────────────────────────────────────────────────────────┐
│ link_buf (循环数组)                                      │
│ [LSN % size] → REDO 长度                                │
│                                                          │
│ log_writer 扫描找到连续区域 → 调用 pwrite 写入 OS Cache │
└─────────────────────────────────────────────────────────┘

关键变量

  • write_lsn:已写入 Page Cache 的位置
  • buf_ready_for_write_lsn:Log Buffer 中连续日志的末尾
  • current_lsn:已分配的最大 LSN
(4)刷盘:log_flusher
  • log_writer 通知 log_flusher
  • log_flusher 调用 fsync 确保持久化
(5)唤醒用户线程

优化策略

  • 条件变量按 Block 分片,减少竞争
  • 专用线程负责唤醒:log_write_notifier / log_flush_notifier

⚙️ 安全级别配置

配置值 说明 安全性 性能
innodb_flush_log_at_trx_commit=1 REDO 刷盘后提交 ⭐⭐⭐⭐⭐ ⭐⭐⭐
=2 仅写入 OS Cache 即提交 ⭐⭐⭐ ⭐⭐⭐⭐⭐

6️⃣ REDO LOG 的安全清理:Checkpoint 机制

REDO 文件空间有限,需定期清理已无需保留的日志。InnoDB 通过 Checkpoint 标记可回收位置。

🎯 Checkpoint LSN 的确定

需确保 Checkpoint 之前的 所有脏页均已刷盘

/* LWM lsn for unflushed dirty pages in Buffer Pool */
const lsn_t lsn = buf_pool_get_oldest_modification_approx();
const lsn_t lag = log.recent_closed.capacity();
lsn_t lwm_lsn = lsn - lag;

/* Note lsn up to which all dirty pages have already been added into Buffer Pool */
const lsn_t dpa_lsn = log_buffer_dirty_pages_added_up_to_lsn(log);

/* Final checkpoint position */
lsn_t checkpoint_lsn = std::min(lwm_lsn, dpa_lsn);

关键概念

  • lwm_lsn:Buffer Pool 中最老脏页的 LSN
  • dpa_lsn:已加入 Buffer Pool 的脏页最大 LSN
  • recent_closed:link_buf,处理脏页注册的乱序问题

💾 Checkpoint 存储

  • 位置ib_logfile0 的 Block 1 和 Block 2(交替写入,防崩溃)
  • 内容
字段 大小 说明
Checkpoint Number 8B 用于判断最新记录
Checkpoint LSN 8B Checkpoint 位置
Checkpoint Offset 8B 文件偏移
Log Buffer Size 8B 暂未使用

恢复时:从最新 Checkpoint LSN 开始重放后续 REDO。


7️⃣ 总结

特性 说明
核心作用 保证事务持久性,支持崩溃恢复
设计思想 Physiological Logging(物理+逻辑混合)
关键机制 LSN 幂等、WAL、Double Write Buffer
性能优化 无锁并发写入、link_buf、专用唤醒线程
空间管理 Checkpoint + 循环文件

REDO LOG 是 InnoDB 实现事务持久性和崩溃恢复的基石。通过 Physiological LoggingLSN 幂等机制三层存储结构 以及 MySQL 8.0 的无锁并发写入架构,InnoDB 在保证数据安全的同时,极大提升了写入性能。

理解 REDO LOG 的价值

  • ✅ 帮助 DBA 优化配置(如 innodb_log_file_sizeinnodb_flush_log_at_trx_commit
  • ✅ 为开发者设计高性能事务应用提供理论基础
  • ✅ 深入理解数据库内核工作原理

8️⃣ 延伸阅读

📚 官方文档与源码

📰 技术文章


📅 本文基于 MySQL 8.0 源码分析

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐