第六篇:Redo Log与Binlog——崩溃恢复的底层保障

前言

在前面的文章中,我们理解了事务的隔离级别和锁机制。但还有一个根本性问题没有解决:事务提交后,数据真的安全了吗?数据库突然宕机,重启后怎么恢复?

这就涉及到MySQL最核心的日志系统------Redo Log和Binlog

面试中,这个问题是区分"会用MySQL"和"理解MySQL"的分水岭:

"Redo Log和Binlog有什么区别?"

"为什么需要两阶段提交?"

"一条UPDATE语句在MySQL中到底经历了什么?"

如果你只能回答"Redo Log用于崩溃恢复,Binlog用于主从复制",面试官会继续追问到你说出两阶段提交的具体流程。本文帮你准备好这个问题的完整答案。

本文核心问题:

  1. WAL(Write-Ahead Logging)是什么?为什么写日志比直接写磁盘快?
  2. Redo Log和Binlog各自的作用和区别?
  3. 一条UPDATE语句在InnoDB中经历了什么?
  4. 两阶段提交(2PC)怎么保证两个日志的一致性?
  5. 崩溃恢复怎么做的?为什么能恢复已提交的事务?
  6. Redo Log的innodb_flush_log_at_trx_commit参数怎么配置?
  7. Binlog的三种格式(STATEMENT、ROW、MIXED)有什么区别?

读完本文,你将对MySQL的日志系统拥有从原理到配置的完整理解。


一、WAL机制------为什么先写日志?

疑问:为什么MySQL不直接把数据写磁盘,而是先写日志?写日志不是多了一步吗?

回答:因为顺序写远快于随机写。直接写数据是随机IO,可能需要寻道+旋转等待;写Redo Log是追加写,磁盘顺序写入的速度接近内存速度。

1.1 没有WAL时

复制代码
UPDATE user SET age = 25 WHERE id = 1;

1. 从磁盘找到id=1所在的数据页(随机IO,约5-10ms)
2. 修改数据页中age的值
3. 将修改后的数据页写回磁盘(随机IO,约5-10ms)

问题:修改一行数据,需要两次随机磁盘IO。如果一次事务修改了100行分散在不同数据页中,就需要200次随机IO,性能完全不可接受。

1.2 有WAL时

复制代码
1. 从磁盘找到id=1所在的数据页(随机读,约5-10ms)
2. 修改Buffer Pool中该数据页的值(内存操作)
3. 将修改记录追加写入Redo Log文件(顺序写,极快)
4. 返回客户端"提交成功"
5. 后台线程异步将脏页刷回磁盘(Checkpoint)

一个修改操作,最多一次随机读(如果数据页已在Buffer Pool中则这一步也可以省掉),加一次顺序写。 写Redo Log是追加写,磁盘磁头不需要移动,效率比随机写高几个数量级。

1.3 WAL的核心思想

用顺序写日志替代随机写数据页,先保障持久性,后台再慢慢同步。


二、Redo Log------物理日志

疑问:Redo Log到底存了什么?为什么它是"物理日志"?

回答:Redo Log记录的是"某个数据页上,在某个偏移量处,修改了什么值"。它是物理层面的记录------哪个表空间、哪个页、哪个位置、改了什么。

2.1 Redo Log的结构

复制代码
Redo Log文件:ib_logfile0, ib_logfile1
循环写入:写完第一个文件写第二个,写满后从头覆盖

Redo Log的记录格式(简化):
┌──────────┬──────────┬──────────┬──────────┐
│ 表空间ID  │  页号    │  偏移量   │  新数据   │
└──────────┴──────────┴──────────┴──────────┘

例如:UPDATE user SET age=25 WHERE id=1
→ 记录:表空间4,页号100,偏移量120,age改为25

2.2 Redo Log的刷盘策略

innodb_flush_log_at_trx_commit 控制Redo Log何时刷盘:

策略 安全性 性能
0 每秒刷盘一次 低(可能丢失1秒内的已提交事务) 最高
1 每次提交时刷盘 高(事务一旦提交就不会丢失) 中等
2 每次提交时写OS缓存,每秒刷盘 中(OS宕机会丢,MySQL宕机不丢) 较高

生产环境建议设为1。 这个决定是在"数据安全性"和"写入性能"之间做权衡------每次提交都刷盘保证了Crash Safe,但磁盘IO压力增大。如果应用可以接受极端情况下丢失1秒的事务(如日志类数据),可以设为0或2换取更高的写入吞吐。

2.3 Redo Log的两阶段刷盘凭证

Redo Log记录了事务ID,且在Prepare阶段已经落盘。崩溃恢复时,事务ID在Redo Log中清晰可辨,这是两阶段提交和崩溃恢复的基础------后面会展开讲。


三、Binlog------逻辑日志

疑问:Binlog和Redo Log有什么不同?为什么MySQL需要两种日志?

回答:Binlog是MySQL Server层的逻辑日志,记录的是SQL语句或行变更的逻辑。Redo Log是InnoDB引擎层的物理日志,记录的是数据页的物理修改。两者职责不同。

3.1 Binlog的三种格式

格式 记录内容 优势 劣势
STATEMENT 记录SQL语句原文 日志量小 部分函数(NOW、UUID)在主从间结果不同
ROW 记录每行数据的具体变更 精确,不会出现不一致 日志量大(批量UPDATE会产生大量行记录)
MIXED 默认用STATEMENT,特殊情况自动切换ROW 兼顾性能与一致性 不够纯粹,排查时不确定到底用了哪种格式

生产建议用ROW。 STATEMENT虽然日志量小,但不确定性函数(NOW、UUID等)导致的主从数据不一致问题更难恢复------数据不一致的修复成本远高于日志量的存储成本。

3.2 Binlog的刷盘策略

sync_binlog 控制Binlog何时刷盘:

策略 安全性
0 交给操作系统决定何时刷盘
1 每次提交时刷盘
N 每N次提交刷盘一次

3.3 Redo Log vs Binlog 核心对比

维度 Redo Log Binlog
产生层 InnoDB引擎层 MySQL Server层
记录内容 物理日志(数据页修改) 逻辑日志(SQL或行变更)
写入方式 循环写(空间固定) 追加写(文件不断增长)
用途 崩溃恢复 主从复制、数据恢复
大小 固定大小(通常几百MB到几GB) 不断增长,需定期清理
刷盘参数 innodb_flush_log_at_trx_commit sync_binlog

四、一条UPDATE语句的完整旅程

疑问:执行 UPDATE user SET age=25 WHERE id=1 ,MySQL内部到底发生了什么?

回答:一条简单的UPDATE语句,在MySQL内部经历了从Server层到引擎层的完整协作。这是面试中展示你理解深度的经典问题。

复制代码
┌─────────────────────────────────────────────────────────────────┐
│  1. Server层 - 连接器:接收客户端连接,获取用户权限             │
├─────────────────────────────────────────────────────────────────┤
│  2. Server层 - 分析器:解析SQL语法,生成语法树                  │
├─────────────────────────────────────────────────────────────────┤
│  3. Server层 - 优化器:选择索引(id=1走主键),生成执行计划      │
├─────────────────────────────────────────────────────────────────┤
│  4. Server层 - 执行器:调用InnoDB引擎接口                       │
│     ↓                                                           │
│  5. InnoDB引擎层:                                              │
│     → 检查id=1的数据页是否在Buffer Pool中                       │
│     → 不在 → 从磁盘读入Buffer Pool                             │
│     → 将修改前的旧值写入Undo Log(用于回滚和MVCC)              │
│     → 修改Buffer Pool中该数据页的age=25                         │
│     → 将修改记录写入Redo Log Buffer(Prepare状态)              │
│     → 返回执行器:修改完成                                      │
│     ↓                                                           │
│  6. Server层 - 执行器:                                          │
│     → 将修改记录写入Binlog Cache                                 │
│     → 提交事务时,先将Redo Log置为Prepare状态并刷盘              │
│     → 再将Binlog刷盘                                            │
│     → 最后将Redo Log置为Commit状态(两阶段提交完成)            │
│     ↓                                                           │
│  7. 返回客户端:"Query OK, 1 row affected"                     │
└─────────────────────────────────────────────────────────────────┘

关键认知:UPDATE操作在Buffer Pool里改完数据页并记录Redo Log后不需要立即刷脏页回磁盘。Redo Log已经记录了这次更改,即使此时宕机也可以在崩溃恢复时重放Redo Log复原数据。脏页由后台线程在合适的时机批量刷入磁盘,这个机制叫做Checkpoint。


五、两阶段提交------保证双日志一致性

疑问:为什么需要两阶段提交?没有两阶段提交会怎样?

回答:两阶段提交确保Redo Log和Binlog要么都写入成功,要么都失败------在任何一个环节宕机,恢复后两个日志保持一致,从而保证主从数据的一致。

5.1 没有两阶段提交的问题

场景一:先写Redo Log,后写Binlog

复制代码
事务A提交:Redo Log写成功 → 此时宕机 → Binlog未写入

恢复后:
  主库通过Redo Log恢复了事务A的修改 ✓
  从库通过Binlog同步,没有事务A的记录 ✗
  → 主从数据不一致!

场景二:先写Binlog,后写Redo Log

复制代码
事务A提交:Binlog写成功 → 此时宕机 → Redo Log未写入

恢复后:
  主库的Redo Log中没有事务A → 主库没有恢复事务A的修改 ✗
  从库通过Binlog同步,有事务A的记录 ✓
  → 主从数据不一致!

5.2 两阶段提交流程

复制代码
阶段一:Prepare
  1. InnoDB将Redo Log标记为Prepare状态
  2. 将Redo Log刷盘

阶段二:Commit
  3. Server层将Binlog刷盘
  4. InnoDB将Redo Log标记为Commit状态(异步,不需要等刷盘)
  5. 事务提交完成

5.3 崩溃恢复时的判断逻辑

复制代码
重启时扫描Redo Log,找到所有处于Prepare状态的事务:

  Prepare状态的事务 → 去Binlog中查找是否有对应的记录
  
  如果Binlog中也有 → 说明两阶段完成 → 提交这个事务
  如果Binlog中没有 → 说明第二阶段完成前就宕机了 → 回滚这个事务

Binlog是判断的依据:Binlog成功写完,说明整个事务流程已经走过最关键的节点,恢复时可以提交;Binlog没写完,说明第二阶段中断,恢复时应该回滚。


六、崩溃恢复的完整流程

疑问:数据库宕机后,重启时是怎么恢复的?

回答:崩溃恢复的本质是"重放Redo Log + 回滚未提交事务"。核心目标是恢复到宕机前最后一个已提交事务的状态。

复制代码
1. 扫描Redo Log,找到所有Prepare状态的事务
2. 去Binlog中查找这些事务的完整记录
3. 如果Binlog中有 → 提交这个事务(重做)
4. 如果Binlog中没有 → 回滚这个事务
5. 对未提交事务,通过Undo Log回滚

已提交但脏页未刷盘的事务:Redo Log中有完整记录 → 重放Redo Log → 数据恢复。这类事务在宕机前"返回了commit成功给客户端,但后台还没来得及把脏页写入磁盘"。Redo Log的存在保证了这些事务的数据不会丢失。

未提交的事务:Redo Log中没有Commit标记 → Binlog中也没有记录 → 通过Undo Log回滚。这类事务在宕机前从未commit过,崩溃恢复后它们应该被当作"从未发生过"。


七、Redo Log与Binlog协同总结

复制代码
Redo Log确保Crash Safe:
  事务提交成功 → Redo Log中有Commit标记 → 即使宕机也能恢复
  
Binlog确保主从一致:
  Binlog中有了记录 → 从库能复制 → 主从数据一致
  
两阶段提交确保双日志一致:
  Prepare(Redo) → Commit(Binlog) → Commit(Redo)
  崩溃恢复时,以Binlog为准判断事务是否真正提交

总结

  • WAL让MySQL的写入速度飞升------用顺序写日志替代随机写数据页,把一次修改的IO成本从两次随机IO降到一次随机读+一次顺序写
  • Redo Log记录页的物理修改(哪个表空间、哪个页、哪个偏移量),用于InnoDB崩溃恢复
  • Binlog记录SQL或行的逻辑变更,用于Server层主从复制和数据恢复
  • 一条UPDATE经历了完整的Server层→引擎层→日志刷盘流程:Buffer Pool修改→Undo Log记录旧版本→Redo Log Prepare→Binlog刷盘→Redo Log Commit
  • 两阶段提交保证双日志一致:Prepare和Commit之间如果宕机,恢复时以Binlog为准------Binlog中有则提交,没有则回滚
  • 崩溃恢复以Binlog为准:Prepare状态的事务看Binlog有没有对应记录来决定提交还是回滚
  • 生产配置建议innodb_flush_log_at_trx_commit=1 + sync_binlog=1最高安全性,2+00+0是性能优先不同安全等级的选择

下一篇预告:MySQL索引原理(七)------慢查询分析与SQL优化实战。整合前六篇的索引和事务知识,用Explain和慢查询日志做真实的SQL优化案例分析。

相关推荐
神仙别闹15 小时前
基于PHP+MySQL实现在线考试系统
开发语言·mysql·php
2501_9219392615 小时前
MySQL(备份恢复、主从复制读写分离)
数据库·mysql
思麟呀16 小时前
MySQL基础CRUD语句
数据库·mysql
0xDevNull16 小时前
MySQL中的锁详解
mysql
Irene199116 小时前
触发器(Trigger) 是数据库中一种特殊的存储程序,它会在指定的表上发生特定事件(如 INSERT、UPDATE、DELETE)时自动执行
mysql
唐青枫17 小时前
别再误会 SELECT 1:MySQL 常量查询与存在性判断实战
sql·mysql
野生技术架构师17 小时前
MySQL / PostgreSQL DDL 审核自动化:从人工 review 到 CI 拦截
mysql·postgresql·自动化
24白菜头17 小时前
MySQL学习笔记
数据库·笔记·学习·mysql
shizhan_cloud17 小时前
MySQL 备份与恢复
数据库·mysql
思麟呀17 小时前
MySQL的内置函数
数据库·mysql