文章目录
- [引言:为什么 MySQL 需要三种日志?](#引言:为什么 MySQL 需要三种日志?)
- 一、要点速览
- [二、Redo Log:崩溃恢复的基石](#二、Redo Log:崩溃恢复的基石)
-
- [2.1 为什么需要 Redo Log?](#2.1 为什么需要 Redo Log?)
- [2.2 Redo Log 的本质](#2.2 Redo Log 的本质)
- [2.3 写入机制与刷盘策略](#2.3 写入机制与刷盘策略)
- [2.4 Checkpoint 与恢复](#2.4 Checkpoint 与恢复)
- [三、Undo Log:原子性与 MVCC 的幕后功臣](#三、Undo Log:原子性与 MVCC 的幕后功臣)
-
- [3.1 事务回滚的保证](#3.1 事务回滚的保证)
- [3.2 MVCC 的核心支撑](#3.2 MVCC 的核心支撑)
- [四、Binlog:Server 层的全局日志](#四、Binlog:Server 层的全局日志)
-
- [4.1 Binlog 的定位](#4.1 Binlog 的定位)
- [4.2 三种格式对比](#4.2 三种格式对比)
- [4.3 Binlog 的写入与刷盘](#4.3 Binlog 的写入与刷盘)
- [五、两阶段提交:Redo Log 与 Binlog 的握手协议](#五、两阶段提交:Redo Log 与 Binlog 的握手协议)
-
- [5.1 为什么需要两阶段提交?](#5.1 为什么需要两阶段提交?)
- [5.2 两阶段提交流程](#5.2 两阶段提交流程)
- [5.3 崩溃恢复时的判断规则](#5.3 崩溃恢复时的判断规则)
- 七、常见误区澄清
- 八、总结
引言:为什么 MySQL 需要三种日志?
作为一款成熟的关系型数据库,MySQL(特别是搭配 InnoDB 存储引擎后)需要在多个看似矛盾的目标之间取得平衡:性能与持久性、并发与一致性、单机恢复与主从复制。单一的日志机制无法同时满足这些需求。因此,MySQL 设计了三种职责分明、彼此协作的日志:Redo Log、Undo Log、Binlog。
理解它们的作用与交互方式,是掌握 MySQL 事务机制、崩溃恢复、MVCC 以及高可用架构的基础。本文将从三个日志的本质出发,逐步深入到两阶段提交协议和完整的协作流程,帮助读者建立起系统化的认知。
一、要点速览
| 日志类型 | 核心作用 | 归属层 | 关键特性 |
|---|---|---|---|
| Redo Log | 保证事务持久性,实现崩溃恢复 | InnoDB | 物理日志、循环写、WAL |
| Undo Log | 保证事务原子性,支持 MVCC | InnoDB | 逻辑日志、版本链、Purge |
| Binlog | 主从复制与时间点恢复 | Server 层 | 逻辑/SQL日志、追加写、2PC |
三者共同实现了 MySQL 的 WAL(Write-Ahead Logging) 架构,并借助两阶段提交保证跨日志的一致性。
二、Redo Log:崩溃恢复的基石
2.1 为什么需要 Redo Log?
如果没有 Redo Log,事务提交时需要立即将修改的数据页刷回磁盘。但数据页的写入是随机 I/O,性能极差。Redo Log 解决了这一矛盾:
- 事务提交时,只写入顺序 I/O 的 Redo Log,后台异步再将数据页刷回磁盘。
这就是 WAL(Write-Ahead Logging) 的核心思想:日志先行,数据滞后。
2.2 Redo Log 的本质
- 物理日志:记录"在某个表空间的某个数据页的某个偏移量处,写入什么值"。
- 循环写:Redo Log 由固定大小的文件(如 ib_logfile0、ib_logfile1)组成,写满后覆盖最老的未刷盘日志(通过 Checkpoint 控制)。
- 重放幂等:多次重放同一 Redo Log 结果相同,适合崩溃恢复。
2.3 写入机制与刷盘策略
Redo Log 并非每次直接写磁盘,而是经过三层结构:
- Redo Log Buffer(内存)
- OS Cache
- 磁盘文件
关键参数 innodb_flush_log_at_trx_commit:
| 值 | 行为 | 持久性 | 性能 |
|---|---|---|---|
| 0 | 每秒刷一次,事务提交不刷 | 可能丢失1秒数据 | 最高 |
| 1 | 每次提交都刷盘(fsync) | 最强 | 较低 |
| 2 | 只写入OS Cache,不保证落盘 | 操作系统崩溃可能丢失 | 较高 |
生产环境推荐 =1,保证 ACID 持久性。
2.4 Checkpoint 与恢复
- Checkpoint:当 Redo Log 写满一定比例或手动触发时,将脏页刷回磁盘,并标记日志可覆盖。
- 崩溃恢复:MySQL 重启后,从最后一次 Checkpoint 开始顺序重放 Redo Log,恢复所有已提交但未刷盘的事务。
三、Undo Log:原子性与 MVCC 的幕后功臣
3.1 事务回滚的保证
Undo Log 的核心职责之一是事务原子性:
- 当事务执行中发生错误或被显式回滚时,利用 Undo Log 将数据恢复为修改前的状态。
与 Redo Log 不同,Undo Log 记录的是逻辑日志,例如:
- "将 id=10 的 name 字段从 'A' 改回 'B'"
- "删除之前插入的某条记录"
3.2 MVCC 的核心支撑
Undo Log 另一个至关重要的角色是多版本并发控制(MVCC)。
- 每次更新操作会生成一个新版本的数据行,同时旧版本保留在 Undo Log 中。
- 多个旧版本通过指针串联形成版本链。
- 不同隔离级别下的 Read View 决定事务能看到哪个版本。
典型场景:
- 可重复读(RR)下,一个事务内的多次查询看到的是同一快照(由最早的 Read View 决定),不受其他事务提交影响。
- 读已提交(RC)下,每次查询生成新的 Read View,能看到已提交的最新数据。
3.3 Purge 线程:版本清理
Undo Log 不能无限增长。当满足以下条件时,Purge 线程会异步清理不再需要的 Undo 版本:
- 该版本在所有活跃事务的 Read View 中都不可见
- 没有更老的事务需要访问它
如果存在长事务,会导致 Undo Log 堆积,影响性能甚至磁盘空间。
四、Binlog:Server 层的全局日志
4.1 Binlog 的定位
与 Redo / Undo Log 不同,Binlog 属于 MySQL Server 层,所有存储引擎(不仅是 InnoDB)都会产生它。
核心用途:
- 主从复制:从库重放 Binlog 实现数据同步。
- 基于时间点的恢复:利用全量备份 + Binlog 恢复到任意时刻。
4.2 三种格式对比
| 格式 | 记录内容 | 优点 | 缺点 |
|---|---|---|---|
| STATEMENT | 原始SQL语句 | 日志量小 | 非确定性函数、存储过程可能导致主从不一致 |
| ROW | 每行数据变更的前后镜像 | 最安全、准确 | 日志量大,大事务可能影响性能 |
| MIXED | 默认STATEMENT,不安全时自动切换ROW | 平衡 | 配置复杂,行为不易预测 |
生产环境一般建议使用 ROW 格式,尤其在有主从复制的场景下。
4.3 Binlog 的写入与刷盘
通过参数 sync_binlog 控制:
- =0:由操作系统决定何时刷盘,性能最好但可能丢日志。
- =1:每次事务提交都刷盘,最安全。
- =N:每 N 个事务刷一次。
主从一致性要求高时,通常设置为 1。
五、两阶段提交:Redo Log 与 Binlog 的握手协议
5.1 为什么需要两阶段提交?
假设没有 2PC:
- 先写 Binlog,再写 Redo Log:写完 Binlog 后崩溃,Redo Log 中无该事务,主从不一致。
- 先写 Redo Log,再写 Binlog:写完 Redo Log 后崩溃,Binlog 无该事务,从库丢失数据。
核心矛盾:Redo Log 负责崩溃恢复,Binlog 负责复制与恢复,两者必须逻辑一致。
5.2 两阶段提交流程
- Prepare 阶段
- 事务修改数据,生成 Redo Log 和 Undo Log。
- Redo Log 写入磁盘并标记为 Prepare 状态。
- Commit 阶段
- 写入 Binlog 并刷盘。
- 将 Redo Log 状态从 Prepare 改为 Commit。
5.3 崩溃恢复时的判断规则
- 若 Redo Log 为 Commit → 提交事务。
- 若 Redo Log 为 Prepare:
- 检查 Binlog 是否完整(包含该事务) → 提交
- 否则 → 回滚
这套机制保证了:任何一个日志中可见的事务,另一个日志中也必然存在。
六、三种日志的协作全景图
以下是一个 UPDATE 语句在执行过程中,三种日志的完整协作流程:
后台异步任务
事务提交阶段
事务执行阶段
崩溃恢复场景
Redo Log = Commit
Redo Log = Prepare
是
否
MySQL 崩溃重启
扫描 Redo Log
提交事务
重放 Redo Log
检查 Binlog
是否包含该事务
提交事务
回滚事务
利用 Undo Log
开始事务
执行 UPDATE 语句
生成 Undo Log
记录旧值
修改 Buffer Pool 中的脏页
生成 Redo Log
状态 = Prepare
将 Redo Log 写入磁盘
顺序 I/O
写入 Binlog 并刷盘
两阶段提交判断
将 Redo Log 状态改为 Commit
事务提交成功
后台线程异步将脏页刷回磁盘
随机 I/O
Purge 线程清理
不再需要的 Undo Log
流程图解读
- 事务执行阶段:Undo Log 先记录旧值(用于回滚和 MVCC),然后修改内存中的脏页,同时生成 Prepare 状态的 Redo Log 并刷盘(顺序 I/O,性能高)。
- 事务提交阶段:先写 Binlog 并刷盘,再将 Redo Log 从 Prepare 改为 Commit。这是两阶段提交的核心,保证 Redo Log 与 Binlog 的一致性。
- 后台异步任务:事务提交后,后台线程会择机将脏页刷回磁盘(随机 I/O),Purge 线程也会清理无用的 Undo Log 版本。
- 崩溃恢复场景:MySQL 重启后根据 Redo Log 状态和 Binlog 完整性决定事务是提交还是回滚。
七、常见误区澄清
| 误区 | 正解 |
|---|---|
| "Redo Log 就是 WAL" | Redo Log 是 WAL 的具体实现,WAL 是一种思想。 |
| "Binlog 也能做崩溃恢复" | Binlog 只能做基于时间点的恢复,不能保证事务原子性恢复。 |
| "Undo Log 只在回滚时有用" | MVCC 下读请求也需要 Undo Log 来构造旧版本。 |
| "两阶段提交影响性能严重" | 顺序刷盘开销可接受,且是保证一致性的必要代价。 |
八、总结
用一句话概括三种日志的分工:
- Redo Log:不怕崩溃,保证你提交的数据不丢。
- Undo Log:不怕回滚,保证你能回到从前;同时也让你读到一致的快照。
- Binlog:不怕主从同步和误删,保证你能重建和恢复。
三者合在一起,才让 MySQL 成为一个既高性能又高可靠、既支持并发又支持复制的成熟数据库系统。
掌握这些日志的原理,不仅仅是应对面试题,更是你日后进行性能调优、故障排查、架构设计(如分库分表、主从延迟分析)的底层能力支撑。