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 Log | InnoDB | 崩溃恢复 | 否 |
| Undo Log | InnoDB | 回滚、MVCC | 是 |
| Binlog | MySQL 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 的事务提交、崩溃恢复、主从复制以及数据一致性问题就能够串联起来。