当前位置: 首页 > news >正文

第六篇: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 LogBinlog
产生层InnoDB引擎层MySQL Server层
记录内容物理日志(数据页修改)逻辑日志(SQL或行变更)
写入方式循环写(空间固定)追加写(文件不断增长)
用途崩溃恢复主从复制、数据恢复
大小固定大小(通常几百MB到几GB)不断增长,需定期清理
刷盘参数innodb_flush_log_at_trx_commitsync_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优化案例分析。

http://www.jsqmd.com/news/774968/

相关文章:

  • AutoJS Pro9.3最新文档详解与入门教程
  • Arm架构通用定时器原理与应用全解析
  • Flutter for OpenHarmony 学习路线实战:从环境搭建到跨端数据持久化全流程解析
  • MYSQL的视图
  • Termi AI:基于Electron的智能桌面开发伴侣,集成Vite预览与AI编程助手
  • 第七篇:慢查询分析与SQL优化实战
  • copilot学生认证按键无法点击
  • golang如何实现桌面应用热更新_golang桌面应用热更新实现攻略
  • MyBatis 高频八股文:从 Mapper 到缓存,一篇搞懂常见面试题
  • Python配置管理实战:从环境变量到类型安全,详解Tanuki单文件库设计
  • #81_闲谈语言的分类
  • linux kernel CONFIG_KCMP解析
  • YOLOv11室内地面塑料袋目标检测数据集-30张-Plastic-Bag-1
  • 微信福音:2345清理王微信专清功能介绍
  • 告别GPIO模拟!用STM32的FSMC高效驱动TFT屏,刷图速度提升实测
  • 吃透C++ STL map/set:从入门到实战,新手也能轻松上手
  • 车载诊断架构---解答售后关于Service 19 06疑问带来的反思
  • 3203黄大年茶思屋榜文保姆级全落地解法「32期3题」量子启发式算法|大规模百万节点图平衡最小分割优化
  • 用Python+PuLP搞定钢管运输优化:手把手复现2000年数模国赛B题
  • 大语言模型如何构建创业者认知代理:从特征工程到RAG应用
  • dotnet-skills:让AI助手掌握现代.NET开发最佳实践
  • 欧拉回路(一笔画)
  • “灵语星火”第二阶段团队记录(一)
  • 如何在华为HarmonyOS设备上部署microG服务:解决签名验证的完整技术指南
  • 开源情报实战指南:从工具到体系的OSINT方法论与自动化实践
  • Emacs光标管理库cursory:实现情境感知的自动切换与主题集成
  • 轻量级唤醒词检测:从MFCC特征到CNN模型在边缘设备的实践
  • 基于工作流的低代码AI应用开发:Flock平台核心架构与实战指南
  • 为什么很多人 DFS 写得飞起,一到「矩阵最长递增路径」就彻底懵了?
  • [特殊字符] 数组中的递增三元组:O(n) 时间高效查找,面试必考!