MySQL Redo Log、Undo Log 与 Binlog

在学习 MySQL InnoDB 时,经常会遇到三个日志:

Redo Log
Undo Log
Binlog

它们名字相似,但解决的问题完全不同。

简单来说:

Redo Log
→ 数据库崩溃后,恢复已经发生的修改

Undo Log
→ 事务回滚、MVCC

Binlog
→ 记录数据库逻辑变更,用于复制、恢复等

其中最容易混淆的是:

为什么 InnoDB 已经有 Redo Log 了,MySQL 还需要 Binlog?

原因在于 Redo Log 和 Binlog 所处的层次、记录内容和设计目标并不相同。

理解三者的关系,也是理解 MySQL 事务提交和主从复制的基础。

1. 为什么数据库需要日志

假设执行:

UPDATE users
SET balance = balance - 100
WHERE id = 1;

最简单的想法是:

修改内存
↓
直接把数据写入磁盘
↓
完成

但真实数据库不能简单这样做。

原因是磁盘 I/O 成本较高,而且一次事务可能修改很多数据页。

如果事务:

修改 1000 个数据页

每次修改都立即同步写磁盘:

修改
↓
写磁盘
↓
修改
↓
写磁盘
↓
...

性能会非常差。

InnoDB 因此采用了类似 WAL(Write-Ahead Logging) 的思想:

修改数据页
      ↓
先记录日志
      ↓
日志持久化
      ↓
数据页后续刷盘

也就是说:

先保证必要的日志信息可靠,再异步处理数据页。

这样既能够保证崩溃恢复能力,又可以减少随机磁盘 I/O。


2. Redo Log:保证崩溃后能够恢复

Redo Log 是 InnoDB 的核心日志之一。

可以把它理解成:

记录“已经对数据做了什么修改”,用于数据库异常重启后的恢复。

例如:

UPDATE accounts
SET balance = balance - 100
WHERE id = 1;

数据页可能已经在 Buffer Pool 中被修改:

磁盘数据页
    ↓
Buffer Pool
    ↓
修改 balance
    ↓
Dirty Page

但 Dirty Page 不一定马上写回磁盘。

此时如果数据库突然宕机:

Buffer Pool
    ↓
内存数据丢失

怎么办?

这时候就需要 Redo Log。

大致过程:

UPDATE
  ↓
修改 Buffer Pool
  ↓
生成 Redo Log
  ↓
事务 COMMIT
  ↓
Redo Log 按持久化策略写入
  ↓
后台异步刷 Dirty Page

如果数据库在 Dirty Page 刷盘之前崩溃:

数据库重启
    ↓
读取 Redo Log
    ↓
重新应用已经记录的修改
    ↓
恢复数据页

因此可以简单记忆:

Redo Log
=
已经发生的修改
+
崩溃恢复时重新执行

3. Redo Log 为什么采用循环写

Redo Log 并不是无限增长的普通日志文件。

InnoDB 会使用固定大小的日志空间进行循环使用。

可以粗略理解为:

┌──────────────────────────────┐
│ Redo Log                     │
│                              │
│ [日志][日志][日志][空闲空间] │
│        ↑                     │
│        当前写入位置          │
└──────────────────────────────┘

写到末尾后继续从前面可复用的位置写入。

因此 Redo Log 的核心特点之一是:

固定空间
+
循环写入

其中有两个重要概念:

LSN
Checkpoint

LSN 可以理解为 Redo Log 中不断增长的日志序列号。

Checkpoint 则代表:

某个位置之前的数据修改已经安全地刷入数据文件,因此这些 Redo Log 空间可以逐渐被复用。

粗略过程:

Redo Log
────────────────────────────
      ↑             ↑
 Checkpoint       Write LSN
      │
      └── 前面的日志可以逐渐回收

如果 Dirty Page 刷盘速度跟不上 Redo Log 写入速度,Redo Log 可用空间就会不断减少。

这也是数据库写入压力过大时需要关注的问题。


4. Undo Log:回滚与 MVCC

Undo Log 和 Redo Log 最大的区别是:

Redo → 向前恢复
Undo → 向后恢复

例如:

START TRANSACTION;

UPDATE users
SET balance = 500
WHERE id = 1;

ROLLBACK;

数据库需要恢复修改前的数据。

假设原值:

balance = 1000

执行:

1000 → 500

如果事务回滚:

500 → 1000

Undo Log 就保存了执行回滚和版本管理所需要的信息。

因此:

UPDATE
 ↓
产生 Undo 信息
 ↓
修改数据
 ↓
ROLLBACK
 ↓
利用 Undo 恢复

Undo Log 还有一个重要用途:MVCC

在 InnoDB 中,普通一致性读并不是简单地读取当前最新版本。

例如:

事务 A
UPDATE balance = 500

与此同时:

事务 B
SELECT balance

在符合 MVCC 条件的情况下,事务 B 可能需要读取旧版本。

这些历史版本与 Undo Log 密切相关。

可以粗略理解为:

当前版本
   ↓
Undo Log
   ↓
历史版本
   ↓
Undo Log
   ↓
更早版本

因此 Undo Log 同时参与:

事务回滚
+
MVCC

5. Binlog:MySQL Server 层的逻辑日志

Binlog 与 Redo Log 最大的区别之一,是它不属于 InnoDB 专属机制。

Binlog 是 MySQL Server 层面的二进制日志。

它主要用于记录:

数据库发生了哪些逻辑变更

典型用途包括:

  • 主从复制;

  • 数据恢复;

  • 增量备份;

  • 数据同步;

  • CDC(Change Data Capture)。

例如:

UPDATE users
SET status = 1
WHERE id = 100;

Binlog 可以记录这次数据变更。

Binlog 常见格式包括:

STATEMENT
ROW
MIXED

其中现代业务系统通常更加关注 ROW 格式。

ROW 模式记录的是行级变更事件,例如:

某张表
某一行
发生了什么数据变化

这样在复制过程中能够更准确地重放数据变化。

可以简单理解:

Redo Log
→ InnoDB 内部恢复机制

Binlog
→ MySQL 层面的变更记录

6. 为什么既需要 Redo Log,又需要 Binlog

这是 MySQL 日志系统最重要的问题之一。

如果只有 Binlog:

SQL
 ↓
修改 Buffer Pool
 ↓
Binlog
 ↓
数据库崩溃

Binlog 本身并不是 InnoDB 用于恢复数据页的内部日志。

InnoDB 仍然需要自己的 Redo Log 来保证存储引擎层面的崩溃恢复能力。

反过来,如果只有 Redo Log:

Redo Log

虽然 InnoDB 可以进行崩溃恢复,但无法很好地承担 MySQL 主从复制所需要的逻辑变更记录。

因此:

Redo Log
→ 存储引擎内部恢复


Binlog
→ MySQL 层面的逻辑变更、复制

二者职责不同,所以都需要存在。


7. 两阶段提交:Redo Log 与 Binlog 如何保持一致

既然存在两套日志,就产生了一个非常关键的问题:

如果 Redo Log 和 Binlog 写入状态不一致怎么办?

例如:

Redo Log 写成功
Binlog 写失败

或者:

Binlog 写成功
Redo Log 没有正确记录

就可能造成:

主库恢复结果
≠
Binlog 重放结果

进而影响数据一致性。

因此 MySQL 在事务提交过程中,需要协调 Redo Log 和 Binlog。

核心机制就是:

Two-Phase Commit,两阶段提交。

可以简化理解为:

事务执行
    ↓
修改 Buffer Pool
    ↓
生成 Undo / Redo
    ↓
Redo Log Prepare
    ↓
写 Binlog
    ↓
Redo Log Commit
    ↓
事务完成

其中:

Prepare

表示 Redo Log 已经进入准备提交状态。

然后写 Binlog。

Binlog 成功后,再完成:

Commit

这样 MySQL 就能够在异常情况下根据日志状态判断事务最终应该如何处理。

为什么这很重要

例如数据库在:

Redo Prepare
↓
Binlog
↓
突然宕机

这个时间点崩溃。

数据库恢复时可以根据:

Redo Log
+
Binlog

判断事务是否已经完成提交。

这就是两阶段提交存在的重要原因。


8. 三种日志放在一起理解

现在可以把三个日志放在一起:

日志所属层次主要作用是否主要用于 MVCC
Redo LogInnoDB崩溃恢复否
Undo LogInnoDB回滚、MVCC是
BinlogMySQL Server复制、恢复、CDC否

可以进一步理解成:

                    MySQL
                      │
             ┌────────┴────────┐
             ↓                 ↓
          Server            InnoDB
             │                 │
          Binlog        ┌──────┴──────┐
                        ↓             ↓
                    Redo Log      Undo Log
                        │             │
                    崩溃恢复        回滚 / MVCC

三者的关注点不同:

Redo
→ “数据库崩溃后怎么恢复?”

Undo
→ “事务怎么回滚?旧版本在哪里?”

Binlog
→ “数据库发生过哪些变更?怎么复制出去?”

9. Binlog 在主从复制中的作用

MySQL 主从复制是 Binlog 最重要的应用场景之一。

简单过程:

                主库
                 │
              执行事务
                 │
              Binlog
                 │
                 ↓
          ┌──────────────┐
          │ Replica      │
          └──────────────┘
                 │
             Relay Log
                 │
                 ↓
              SQL/Worker
                 │
                 ↓
              从库数据

主库执行事务后:

主库
 ↓
产生 Binlog
 ↓
从库获取 Binlog
 ↓
写入 Relay Log
 ↓
复制线程/工作线程执行
 ↓
更新从库

因此主从复制与 Redo Log 并不是一回事。

可以理解为:

Redo Log
→ 本机 InnoDB 崩溃恢复

Binlog
→ 把数据变更传播到其他 MySQL 实例

这也是为什么:

主从复制主要依赖 Binlog,而不是直接复制主库的 Redo Log。


10. 总结

MySQL 的三种核心日志可以用一句话区分:

Redo Log
→ 已经修改的数据,崩溃后怎么恢复

Undo Log
→ 修改之前的数据,事务怎么回滚、MVCC 怎么读取历史版本

Binlog
→ 数据库发生了什么变化,怎么复制和同步

一次事务可以粗略理解为:

BEGIN
  ↓
执行 SQL
  ↓
修改 Buffer Pool
  ↓
生成 Undo
  ↓
生成 Redo
  ↓
Redo Prepare
  ↓
写 Binlog
  ↓
Redo Commit
  ↓
COMMIT

数据库异常时:

Redo Log
→ 负责 InnoDB 崩溃恢复

事务回滚或 MVCC:

Undo Log
→ 提供历史版本和回滚能力

主从复制:

Binlog
→ 将数据库变更传播给 Replica

因此,不应该简单地认为:

“Binlog 就是数据库日志,Redo Log 也是数据库日志,所以两者重复。”

它们实际上处于不同层次,承担不同职责:

              MySQL
                │
        ┌───────┴───────┐
        ↓               ↓
      Binlog          InnoDB
        │          ┌────┴────┐
        │          ↓         ↓
        │       Redo       Undo
        │          │         │
        ↓          ↓         ↓
      复制       崩溃恢复   回滚/MVCC

理解这三个日志之后,MySQL 的事务提交、崩溃恢复、主从复制以及数据一致性问题就能够串联起来。