InnoDB 中有三类核心日志,各自承担着不同却又互补的职责。理解它们,你就能明白 MySQL 是如何做到“既快又稳”的——即使是崩溃断电,提交了的数据也不会丢,没提交的数据也不会乱。
3.4.1 Redo Log(重做日志):保证事务的持久性
Redo Log 负责“重做”已经提交的事务。它的作用可以浓缩为一句话:一旦事务提交,Redo Log 就已经把修改的内容记录下来了,即使数据库立刻崩溃,重启后也能按日志把数据恢复出来。
- 物理日志,顺序写性能高
Redo Log 记录的是“在某个数据页的某个偏移量上做了什么修改”,是一种物理日志,日志体量小、写入快。Redo Log 文件是固定大小的循环写结构,写满一圈后会从头覆盖最早的内容——因此 Redo Log 只保存最近一段时间内的修改,不用于长期存档。
- WAL 机制:先记日志再写数据
InnoDB 采用 Write-Ahead Log(WAL)机制。修改数据时,并不会立即把数据页刷入磁盘,而是先记录 Redo Log,把随机写(数据页分散在不同位置)转换成对 Redo Log 文件的顺序写,然后就可以返回提交成功。被修改的脏页由后台线程延迟刷盘。这样事务的响应快得多。
- 刷盘策略与性能取舍
Redo Log 的刷盘时机由 innodb_flush_log_at_trx_commit 参数控制:
- 设为 1(默认,最安全):每次事务提交时,将 Redo Log Buffer 写入文件系统缓存并调用
fsync刷盘。崩溃后最多丢失一个事务,但每次提交多一次磁盘同步,性能开销最大。 - 设为 2(折中):每次提交时,将日志写入文件系统缓存,每秒刷一次盘。MySQL 进程崩溃不会丢,但操作系统崩溃最多丢失 1 秒数据。
- 设为 0(最快,不安全):每秒将缓冲写入文件系统缓存并刷盘,事务提交时不主动写日志。MySQL 崩溃可能丢失 1 秒的上一个事务。
绝大多数生产环境坚持使用 1。只有在允许少量数据丢失且追求极限吞吐量的场景下,才会考虑 2。
- 崩溃恢复流程
MySQL 重启时,InnoDB 会检查 Redo Log 和数据页的 LSN(日志序列号),找出需要重做的页。通过回放 Redo Log,将已提交但未刷盘的脏页恢复到最新状态。这个过程完成前,数据库无法对外提供服务。
一句话总结:Redo Log 是 InnoDB 的“保险绳”,只要日志落盘,即使数据文件没来得及更新,数据也不会丢。
3.4.2 Undo Log(撤销日志):支撑事务回滚与 MVCC
Undo Log 负责“撤销”未提交的修改,同时支撑多版本并发控制(MVCC)。它记录的是“数据修改前的旧版本”,比如把某列的值从 200 改成 300,Undo Log 里会记下“该列原始值是 200”。
- 事务回滚的原理
如果事务执行了 ROLLBACK 或因为崩溃在重启后被标记为未提交,InnoDB 就顺着 Undo Log 反向操作,把数据恢复成修改前的样子。这是一个物理过程:从最新的 Undo Log 记录向前推,逐步还原数据页。
- MVCC 的无锁读
在可重复读隔离级别下,一个事务启动时会生成一个 Read View,之后所有普通的 SELECT 都基于这个视图。当一行数据被其他事务修改了,Undo Log 中保留下旧版本,读事务通过 Undo Log 的记录链(版本链)找到它可见的那个版本,从而不用加锁也能读到一致的数据。这避免了读写冲突,大大提升并发能力。
- 存储位置与清理机制
Undo Log 默认存储在共享表空间(或独立 Undo 表空间)中,以段的形式管理。事务提交后,对应的 Undo Log 并不会立刻删除,因为可能有其他活跃事务的 Read View 还在依赖它。InnoDB 的后台线程 purge 会定期清理那些不再被任何事务可见的旧版本,释放空间。长事务会导致 Undo Log 不能及时清理,膨胀表空间,甚至拖慢整体性能,这是实际运维中必须留意的。
简而言之:Undo Log 为回滚提供了逆向操作,为 MVCC 提供了历史版本,是事务隔离和一致性读的幕后功臣。
3.4.3 Binlog(二进制日志):数据备份与主从复制的基石
Binlog 是 MySQL Server 层产生的逻辑日志,独立于存储引擎,记录的是引起数据变更的 SQL 语句或行级别的变更事件。
- 不是引擎层日志,而是服务器层日志
无论你用的是 InnoDB 还是 MyISAM,只要发生了数据变更,Server 层就会产生 Binlog。它是一个无限膨胀的文件序列,不会自动覆盖,需要定期清理或归档。
- 三种记录格式
- STATEMENT:记录执行的 SQL 语句。体积小,但某些不确定性函数(如 NOW()、UUID())会导致主从数据不一致。
- ROW(推荐):记录每一行被修改前后的具体值。体积较大,但精确无误,能保证主从绝对一致。MySQL 5.7.7 以后默认使用该格式。
- MIXED:混合模式,默认使用 STATEMENT,当发现可能产生不一致时自动切换为 ROW。兼顾体积和准确性,但行为不如 ROW 可预测。
生产环境绝大多数场景应使用 ROW 格式,尤其在主从复制架构中。
- 写入与刷盘控制
Binlog 的刷盘由 sync_binlog 控制:
- 设为 1(最安全):每次事务提交前将 Binlog 的写缓存同步到磁盘。崩溃后不丢 Binlog 事件。
- 设为 N:每 N 次事务提交后同步一次。提升性能但操作系统崩溃可能丢失最近几个事务的 Binlog。
- 设为 0:由操作系统自行决定刷新时机,最快但最不安全。
- 核心作用
- 主从复制:从库的 I/O 线程拉取主库的 Binlog,写入 Relay Log,然后由 SQL 线程重放,实现数据同步。这是 MySQL 横向扩展和读写分离的基础。
- 基于时间点恢复:结合全量备份和 Binlog,可以回放备份点之后的所有 Binlog 事件,恢复到任意指定时刻。比如误删表后,通过全量备份恢复到删除前的完整数据库,再重放到删除之前一刻的 Binlog 位置,精确恢复数据。
- 数据审计与变更追踪:通过解析 Binlog,可以跟踪所有数据变更记录,常用于异构数据同步(如 Canal 同步到 Kafka/Elasticsearch)。
3.4.4 两阶段提交:保证 Redo Log 与 Binlog 的一致性
一个写入操作既涉及 InnoDB 层的 Redo Log,又涉及 Server 层的 Binlog。如果两者写入不同步,可能导致崩溃后主从数据不一致。比如:
- 先写 Redo Log 再写 Binlog:写完 Redo Log 后崩溃,从库没有接收到该事务,但主库恢复后却认为该事务已提交,造成主从数据差异。
- 先写 Binlog 再写 Redo Log:写完 Binlog 后崩溃,主库重启后因为 Redo Log 不完整会回滚该事务,但从库已经重放了 Binlog 中的该事务,同样造成差异。
两阶段提交正是为了解决这个问题,确保两个日志在逻辑上同时写入成功:
- Prepare 阶段:InnoDB 将事务的 Redo Log 写入并标记为 prepare 状态,然后等待 Server 层处理。
- 写入 Binlog:Server 层将事务对应的 Binlog 事件写入文件。
- Commit 阶段:Binlog 写成功后,InnoDB 将 Redo Log 的 prepare 状态切换为 commit,事务正式提交。
崩溃恢复时,MySQL 会检查:
- 已提交的事务(Redo Log 为 commit):直接完整恢复。
- Redo Log 处于 prepare 但 Binlog 中没有对应记录:认为事务没完成,回滚。
- Redo Log 处于 prepare 且 Binlog 中有对应记录:认为事务已经完整记录,提交以防主从丢失。
这个机制保证了在任意崩溃场景下,Redo Log 和 Binlog 的数据状态一致,主从数据也必然一致。对开发者来说,这意味着你不需要额外编码去处理日志不一致的异常,数据库内核已经给你兜底了。
3.4.5 三类日志的对比与记忆要诀
| 特性 | Redo Log | Undo Log | Binlog |
|--------|------------------------------|--------------------------|----------------------------|
| 产生层 | InnoDB 引擎层 | InnoDB 引擎层 | MySQL Server 层 |
| 内容 | 物理日志(页修改记录) | 逻辑日志(旧版本数据) | 逻辑日志(SQL 或行变更) |
| 作用 | 崩溃恢复,保障持久性 | 事务回滚,支撑 MVCC | 主从复制,时间点恢复 |
| 写入时机 | 事务进行中,提交时刷盘 | 事务开始时生成,提交后待清理 | 事务提交时写入,提交前刷盘 |
| 存储形式 | 固定大小的循环文件 | 表空间中的回滚段 | 无限追加的文件序列 |
记忆要诀:Redo 保“重来”,Undo 保“撤回”,Binlog 保“复制”。当你排查崩溃恢复问题时先看 Redo,排查回滚或长事务问题看 Undo,排查主从同步或数据恢复问题看 Binlog,三者各司其职又缺一不可。