Redo / Undo / WAL(为什么 MySQL 写比读复杂)

🔥 一句话先破题

MySQL 的所有写操作 = 改内存 + 写日志(两个日志) → 最后才慢慢刷磁盘数据页。

原因是:

直接改磁盘太慢,且不安全,必须通过日志来“骗过磁盘的慢”。

这就是 WAL(Write-Ahead Logging)的本质。


🍔 先上总图(最通俗的写流程,助你先有大局观)

你执行:

UPDATE user SET age=20 WHERE id=1;

InnoDB 实际上做了 4 件事:

  1. 生成 Undo Log:记录“怎么回滚”
  2. 改内存中的数据页(Buffer Pool)
  3. 生成 Redo Log:记录“怎么重做”
  4. 先把 Redo Log 落盘(刷到 redo log file)
    这是 WAL:先写日志,再写数据

最终数据页还是在内存里,过一段时间才被刷回磁盘(flush)。

为什么要搞这么多环节?
因为磁盘太慢了,数据库只能耍聪明。


🟦 1. Undo Log:用来“穿越时间”的日志

Undo Log 是 MySQL 自己的“时光倒流机器”。

用途:

  • 事务回滚(roll back)
  • MVCC 读取旧版本(快照读)

你更新 age=20,那么 Undo Log 会记录:

原来的 age=18
原来的 row trx_id=xxx

也就是:
我怎么把这一行恢复到更新前?

Undo Log 存在 Undo Segment 里,属于“逻辑日志”。


🟥 2. Redo Log:保障 Crash-Safe 的日志(比 Undo 更底层)

Redo Log 不是为了回滚,而是为了 宕机恢复(Crash Recovery)

Redo Log 记录的是:

我对某个页做了哪些物理操作(偏移量 + 改了什么值)

也就是:

数据库重启后,我可以照着 redo log 把内存里的修改重新“补”到磁盘页上。

Redo Log 是物理日志,且是循环写的(固定大小文件,如 2GB)。


🧊 Undo + Redo 的关系指南(最容易混)

你可以这样记:

目标 用哪个
想回到过去(回滚) Undo Log
想补上未来(崩溃恢复) Redo Log
想恢复旧版本记录给 MVCC Undo Log

Undo 是 “回滚我自己”
Redo 是 “重做我做过的事”

两个日志互补,缺一不可。


🧠 3. WAL:Write-Ahead Logging(写之前先记账)

核心原则:

数据还没写盘可以,但 redo log 必须先写盘。

为什么?
因为如果只改了内存,MySQL 崩了,那数据就丢了。

所以写流程如下:

BEGIN;
改内存页 → 写 redo buffer → 刷 redo log(fsync) → COMMIT

数据页本身可以晚点刷盘(这叫异步刷盘),
但 redo log 必须先落盘,才能保证“崩了不丢”。

这就是 WAL 的灵魂。


🎬 4. 整个写查询的动画版(让你脑补一遍)

🔔 步骤 1:生成 Undo Log

为了能回滚 → 先记录旧值

Undo Log: {id=1, old age=18}

🧱 步骤 2:修改内存页(Buffer Pool)

页还没刷到磁盘,只是在 RAM 里改了一份。

📝 步骤 3:生成 Redo Log(InnoDB Log Buffer)

记录“我改了哪个页、偏移量、改了什么字段”。

🔥 步骤 4:刷 Redo Log 到磁盘

这是 commit 时最关键的一步
也是 MySQL 写性能瓶颈的来源(fsync)

保证崩机也能恢复

🕒 步骤 5:后台慢慢把数据页刷盘(异步)

最终数据会被刷到磁盘的 .ibd 文件。

注意:这个步骤跟 commit 无关,不需要马上做。


🧨 为什么 MySQL 写比读复杂?(重点解释)

读:

  • 看 Buffer Pool
  • 没有 → 读一页
  • 完事

写:
需要:

✔ 1. 生成 Undo Log

为了回滚 + MVCC 历史版本

✔ 2. 改 Buffer Pool

脏页(dirty page)形成

✔ 3. 生成 Redo Log

为了 crash-safe

✔ 4. WAL:Redo 先刷盘(昂贵的 fsync)

这是必须的

✔ 5. 后台还要刷脏页(checkpoint)

由后台线程专门处理

✔ 6. Redo Log 是循环写 → 还要配合 checkpoint

避免覆盖

写操作包含:

  • 两份日志(Undo/Redo)
  • 两次写(redo write、data page write)
  • 事务管理
  • MVCC 版本链维护
  • 缓冲池管理

写要做的事情至少是读取的 5〜10 倍。


🧩 再讲一个你一定会懂的比喻:

想象你写论文:

读论文 = 打开文件 → 读一小段 → 做笔记 → 关掉

写论文 =

  1. 做备份(Undo)
  2. 打草稿(改内存页)
  3. 写修改日志(Redo)
  4. 把日志保存到本地(WAL:fsync)
  5. 最后再慢慢把最终版本保存(Checkpoint)

写比读累得多这一点,就很像 MySQL。


🎯 最终总结一张图(你面试用它吊打别人)

            +---------------------+
            |    Client SQL       |
            +---------+-----------+
                      |
                      v
        +------------------------------+
        |      1. Undo Log(逻辑)     |
        +------------------------------+
                      |
                      v
        +------------------------------+
        | 2. 修改 Buffer Pool 页(内存)|
        +------------------------------+
                      |
                      v
        +------------------------------+
        |   3. Redo Log(物理日志)     |
        +------------------------------+
                      |
                      v
        +------------------------------+
        |   4. WAL:先写 redo 文件     |
        |     (fsync, 最关键最慢)      |
        +------------------------------+
                      |
             事务提交成功
                      |
                      v
        +------------------------------+
        |   5. 后台刷脏页到磁盘(慢)   |
        +------------------------------+
posted @ 2025-12-06 14:33  中登程序猿  阅读(0)  评论(0)    收藏  举报