Appearance
MySQL 日志、事务提交与崩溃恢复
很多人学 MySQL 日志时,会把它记成几个名词:
redo logundo logbinlog
但如果只是背名字,往往还是很容易卡在关键区别上:
- 它们分别解决什么问题
- 为什么一个写操作要记这么多日志
redo log和binlog到底有什么区别- 两阶段提交是为了解决什么
- 数据库宕机后,为什么有的事务能恢复,有的不能
这篇文章就专门把这条线讲清楚。
1. 🌟 先记结论:三类日志分别负责什么
如果只想抓住最核心的区别,可以直接记:
undo log:为了回滚和 MVCCredo log:为了崩溃恢复binlog:为了主从复制和归档恢复
它们不是重复设计,而是在解决三个不同的问题:
- 事务还没完成时,怎么撤销已经做过的修改
- 数据页还没来得及刷盘时,机器宕了怎么恢复
- 这次变更怎么同步给从库,或者之后做数据回放
2. 为什么 MySQL 不能只靠数据页本身
很多人第一次接触数据库时,会自然地想:既然最终数据都在表文件里,那直接改数据页不就行了?
问题在于,磁盘写很慢,而且一次事务提交不可能要求:
- 立刻把所有相关数据页同步写盘
- 还要保证宕机后绝对不丢
- 同时保持高并发吞吐
如果每次提交都强制把所有脏页立刻刷盘,代价会非常高。
所以数据库采用了更符合工程现实的做法:
- 先在内存页里修改
- 把关键日志写下来
- 数据页在合适时机再刷盘
这背后的核心思想就是:先写日志,再落数据页。
也就是常说的 WAL:Write-Ahead Logging。
3. undo log:解决“怎么回退”和“怎么看旧版本”
3.1 它在解决什么问题
undo log 主要服务两个目标:
- 事务回滚
- MVCC 的一致性读
你可以把它理解成:在修改数据之前,把旧版本记下来。
这样如果事务失败了,就可以回滚;如果别的事务需要看旧版本,也能顺着版本链找到历史状态。
3.2 一个简单例子
假设账户余额原本是:
text
balance = 100事务把它改成:
text
balance = 150那数据库在改之前,通常会把旧值 100 相关信息记到 undo log 里。
这样:
- 如果事务回滚,可以恢复成
100 - 如果其他事务当前不能看到新版本,也可以读到旧版本
100
3.3 它和 MVCC 的关系
MVCC 之所以能做到“普通 select 不加锁也能读到一致性数据”,靠的并不只是事务 ID,还包括:
- 记录里的隐藏列
undo log形成的版本链Read View
所以你可以把 undo log 理解成:MVCC 的历史版本仓库。
3.4 注意点
undo log不只是给回滚用- 长事务会让旧版本长期不能清理
- 版本链太长会让查询和回收成本上升
这也是为什么工程上一直强调:尽量避免长事务。
4. redo log:解决“宕机后怎么把已经提交的修改补回来”
4.1 它在解决什么问题
数据库更新时,真正的数据页通常先改内存里的 Buffer Pool,并不会每次提交都立刻落盘。
这时问题就来了:如果事务已经提交,但数据页还没刷到磁盘,机器突然宕机怎么办?
这就是 redo log 要解决的事。
它记录的是:某些数据页做过哪些修改,宕机恢复时可以重做。
4.2 为什么 redo log 比直接刷盘更划算
因为直接刷整页很重,而写日志通常更顺序、更紧凑。
所以数据库更常见的做法是:
- 内存页先改
redo log先按策略落盘- 数据页稍后再刷盘
这样可以同时兼顾:
- 提交速度
- 崩溃恢复能力
- 磁盘写入效率
4.3 redo log 的核心价值
一句话总结就是:只要 redo log 足够可靠,数据页就不必在提交瞬间强制刷盘。
5. 🌟 binlog:解决“怎么复制”和“怎么做归档恢复”
5.1 它在解决什么问题
binlog 是 MySQL Server 层的逻辑日志,最主要用于:
- 主从复制
- 数据归档
- 基于时间点恢复
它更关心的是:这次事务从业务语义上做了什么变更。
5.2 🌟 它和 redo log 的本质区别
最容易混的是这两个:
redo log:InnoDB 层,偏物理恢复,面向崩溃恢复binlog:Server 层,偏逻辑变更,面向复制和归档
所以它们不是谁替代谁,而是:一个负责把本机数据救回来,一个负责把变更传出去或留档。
5.3 常见 binlog 格式
常见有三种:
statementrowmixed
statement
记录 SQL 语句本身。
优点是:
- 日志量可能更小
缺点是:
- 某些非确定性语句复制时可能不稳定
row
记录每一行实际变成了什么。
优点是:
- 复制更准确
- 可重放性更强
缺点是:
- 日志量可能更大
现在实际业务里,很多场景会更偏向 row。
6. redo log、undo log、binlog 到底怎么配合
如果你把一次更新简化来看,大致会经历下面几步:
- 事务开始
- 先生成
undo log - 修改内存中的数据页
- 记录
redo log - 事务提交时写入
binlog - 按策略把日志真正刷盘
- 数据页稍后再刷盘
这三者解决的问题完全不同:
mermaid
flowchart LR
A[更新一行数据] --> B[undo log<br/>记录旧版本]
A --> C[redo log<br/>记录可重做修改]
A --> D[binlog<br/>记录逻辑变更]
B --> E[回滚 / MVCC]
C --> F[崩溃恢复]
D --> G[主从复制 / 归档恢复]7. 🌟 为什么会有两阶段提交
7.1 背景问题
如果一个事务既要写 redo log,又要写 binlog,那就必须把一个关键问题想清楚:
如果只写成功了一部分,宕机了怎么办?
因为这两份日志分别服务不同目标:
redo log关系到本机恢复binlog关系到复制和归档
如果它们状态不一致,就会出问题。
7.2 一个典型不一致场景
假设没有两阶段提交:
- 事务已经把
redo log写好了并标记提交 - 但
binlog还没写成功 - 这时机器宕机
恢复后,本机会认为事务已经提交,因为 redo log 在。
但从库拿不到这次变更,因为 binlog 没有。
这样主从数据就可能不一致。
反过来,如果 binlog 写了,但 redo log 没提交,也会有另一种不一致风险。
7.3 两阶段提交的大致思路
核心流程可以简化成:
- 先写
redo log prepare - 再写
binlog - 最后把
redo log标记为commit
这样恢复时就可以根据日志状态判断:
- 只有
prepare,但没有完整binlog,通常不能算真正提交 prepare和binlog都完整,恢复时可以把事务补成提交
7.4 为什么它能降低不一致问题
因为它给“提交”这件事引入了一个中间状态。
数据库不再简单地认为:redo log 一写完就绝对提交。
而是先进入 prepare 状态,等 binlog 也准备好后,再真正完成提交。
8. 两阶段提交的流程图
mermaid
sequenceDiagram
participant TX as 事务
participant InnoDB as InnoDB
participant Binlog as Binlog
TX->>InnoDB: 写 undo log,修改 Buffer Pool
TX->>InnoDB: 写 redo log prepare
TX->>Binlog: 写 binlog
TX->>InnoDB: 写 redo log commit
Note over TX,Binlog: 到这里事务才算完整提交9. 🌟 宕机恢复时数据库大致会做什么
很多人会把“崩溃恢复”想成一句空话,其实可以拆成两个方向:
- 把该恢复的修改补回来
- 把不该保留的修改撤掉
大体可以这样理解:
- 已提交但数据页没刷盘的事务,靠
redo log重做 - 未提交的事务,靠
undo log回滚
所以崩溃恢复并不是只靠一份日志,而是:redo log 负责向前补,undo log 负责向后撤。
10. 一个完整视角:从事务提交到崩溃恢复
把整个过程串起来,可以这样记:
- 更新前先留旧版本,保证能回滚,也保证 MVCC 能看历史
- 更新时先改内存页,提高写入效率
- 提交前把
redo log按策略写稳,保证宕机后能补做 - 同时写
binlog,保证复制和归档能力 - 通过两阶段提交让
redo log和binlog尽量保持一致 - 宕机恢复时,已提交事务用
redo log恢复,未提交事务用undo log回滚
这条线如果能真正讲顺,MySQL 日志这一块基本就有了比较完整的理解。
11. 最容易混淆的几个问题
11.1 🌟 redo log 和 binlog 有什么区别
可以从这几个维度理解:
- 所在层次不同:
redo log属于 InnoDB,binlog属于 MySQL Server - 作用不同:
redo log用于崩溃恢复,binlog用于复制和归档 - 记录方式不同:
redo log更偏物理恢复信息,binlog更偏逻辑变更
11.2 🌟 为什么 MySQL 既要 redo log 又要 binlog
因为两者解决的问题不同:
redo log解决本机宕机恢复binlog解决主从复制和归档回放
如果只有其中一个,能力就不完整。
11.3 🌟 undo log 是不是只用来回滚
不是。
它除了支持回滚,还支撑了 MVCC 的旧版本读取。
11.4 🌟 两阶段提交是为了解决什么
是为了解决 redo log 和 binlog 在事务提交时可能出现的不一致问题,尽量保证:
- 本机恢复状态正确
- 主从复制状态也正确
11.5 🌟 崩溃恢复依赖哪些日志
更完整的理解是:
- 已提交事务的数据补做主要依赖
redo log - 未提交事务的回滚主要依赖
undo log binlog更主要面向复制和归档,不是 InnoDB 崩溃恢复的核心日志
12. 一段压缩版说明
如果只想用一段话把这三类日志串起来,可以这样概括:
undo log 主要用于事务回滚和 MVCC,它保存的是修改前的旧版本信息;redo log 主要用于崩溃恢复,因为 InnoDB 更新数据时通常先改内存页,再把可重做的修改写入 redo log,只要 redo log 安全落盘,宕机后就能把已提交但未来得及刷盘的数据补回来;binlog 是 MySQL Server 层的逻辑日志,主要用于主从复制和归档恢复。因为一个事务提交时既涉及 redo log,又涉及 binlog,所以 MySQL 通过两阶段提交尽量保证两者状态一致,避免本机恢复成功但从库没同步到,或者反过来的不一致问题。
13. 工程上几个容易被忽略的点
13.1 长事务会拖累 undo 清理
长事务会让旧版本长期不能回收,带来:
- undo 膨胀
- 版本链变长
- 查询和回收变慢
13.2 不要把 binlog 当成 redo log 的替代品
二者职责不同。
很多人一看到“日志”就会混成一类,但工程上它们分别服务不同目标。
13.3 事务提交成功不等于数据页已经落盘
实际写项目时,更多是这样,很多时候提交成功意味着:
- 关键日志已经按策略写稳
- 数据页可以稍后刷盘
这正是数据库能兼顾性能和持久性的关键。
14. 总结
如果把这篇文章压缩成最核心的几句话,就是:
undo log负责“能撤销”和“能回看”redo log负责“宕机后能补做”binlog负责“把变更传出去和留档”- 两阶段提交负责尽量让
redo log和binlog一致 - 崩溃恢复时,已提交事务靠
redo log恢复,未提交事务靠undo log回滚
真正理解 MySQL 日志,不是记住三个名词就结束了,而是要看清:
一次事务写入,为什么要同时面对回滚、恢复、复制这三类问题。