MySQL InnoDB异常崩溃后的数据自动恢复机制
如果 MySQL 突然断电或者进程崩溃,Buffer Pool 中的数据全没了,InnoDB 到底是怎么恢复数据的?
例如服务器宕机前可能是:
事务 A: 已经 COMMIT 但是部分 Dirty Page 还没有刷盘 事务 B: 还没有 COMMIT 但是部分修改过的 Page 可能已经被刷到了磁盘
突然:
MySQL 崩溃
这时候就同时存在两个问题:
事务 A: 该保留的修改可能还没写入数据文件 事务 B: 不该保留的修改却可能已经写入数据文件
InnoDB 怎么处理?
答案就是:先利用 redo log 恢复应该存在的数据页修改,再利用 undo log 回滚没有提交完成的事务。
这就是 Crash Recovery 的核心思想。
1. 什么是 Crash Recovery?
Crash Recovery 就是:MySQL 在发生异常宕机之后,重新启动时自动把 InnoDB 恢复到一致状态的过程。
例如:
- 操作系统突然崩溃
- 服务器断电
mysqld进程被强制终止- 机器突然重启
都可能导致:
Buffer Pool 中的内容直接丢失
但是磁盘上的:
redo logundo log
数据文件等仍然存在。InnoDB 就利用这些持久化信息完成恢复。MySQL 官方也把事务、COMMIT、ROLLBACK 和 crash recovery 作为 InnoDB ACID 能力的重要组成部分。
2. 为什么崩溃以后不能直接使用磁盘数据?
因为:事务提交成功,并不意味着所有 Dirty Page 都已经写回磁盘。
例如执行:
UPDATE account SET balance = 900 WHERE id = 1;
事务已经:COMMIT
但可能仍然是:
Buffer Pool: balance = 900 磁盘数据文件: balance = 1000
因为内存中的 Page 是 Dirty Page。
只要对应 redo log 已经满足持久化要求,事务就不需要等待整个数据页刷盘。
如果此时突然宕机:
Buffer Pool ↓ 全部丢失
磁盘里却还是:
balance = 1000
所以如果 MySQL 重启以后直接相信数据文件:已经提交的 900 就丢了。
这就是为什么需要 redo log。
3. redo log 在崩溃恢复中做什么?
redo log 记录的是数据页发生修改时所需要的重做信息。
它最大的作用之一就是:恢复那些已经发生、但还没有完整写入数据文件的 Page 修改。
例如:
磁盘: balance = 1000 redo: 这个 Page 后来发生过修改 1000 → 900
MySQL 重启以后:
读取 redo
↓
重新应用需要恢复的修改
↓
balance = 900这就是 redo 这个名字的含义:
redo = 再做一次
MySQL 8.4 官方明确说明,redo log 是用于 crash recovery 的磁盘结构;异常关闭之前没有完成写入数据文件的修改,会在初始化时自动重新应用。
4. 那 undo log 又是干什么的?
现在再看另一种情况。
假设事务 B:
START TRANSACTION; UPDATE account SET balance = 500 WHERE id = 2;
但是还没有:
COMMIT;
服务器就崩了。
你可能会想:没有 COMMIT,修改应该还只存在 Buffer Pool,宕机以后内存丢了,不就自动恢复了吗?
不一定。
因为 InnoDB 允许脏页在事务提交之前就刷入磁盘。
例如:
事务 B:
balance = 1000
↓
修改
↓
balance = 500
但还没有 COMMIT此时某个 Page 可能已经被后台刷盘:
磁盘: balance = 500
然后突然:
💥 宕机
那么磁盘上已经出现了:一个未提交事务的修改。
这时候就需要 undo log。
5. 所以 redo 和 undo 分别解决什么问题?
redo:把应该存在的修改重新做出来。undo:把不应该存在的修改撤销掉。
例如:
事务 A: 已经 COMMIT 但是 Page 没刷盘 ↓ redo 恢复
而:
事务 B: 没有 COMMIT 但是 Page 已经刷盘 ↓ undo 回滚
所以整个恢复过程可以简单理解为:
Crash ↓ redo ↓ 把数据页恢复到崩溃时应该具有的状态 ↓ undo ↓ 回滚未完成事务 ↓ 得到一致的数据状态
这就是 redo + undo 的核心配合。
6. 例子
假设初始数据:
A = 100 B = 100
现在有两个事务。
事务 T1:
UPDATE account SET balance = 200 WHERE id = 1; COMMIT;
事务 T1 已经提交。
但是:
对应 Dirty Page 还没有刷盘
所以磁盘可能仍然:
A = 100
与此同时事务 T2:
START TRANSACTION; UPDATE account SET balance = 300 WHERE id = 2;
但是 T2:没有 COMMIT
恰好 T2 修改的数据页已经被后台刷盘。
于是宕机前:
T1: 应该是 A = 200 但磁盘还是 A = 100 T2: 不应该保留 B = 300 但磁盘已经是 B = 300
突然:
💥 Crash
磁盘现在可能是:
A = 100 B = 300
但正确结果应该是:
A = 200 B = 100
怎么办?
7. 第一步:先应用 redo
MySQL 重启以后,InnoDB 会进行 redo log application。
根据 redo,把需要恢复的 Page 修改重新应用。
于是:
磁盘:
A = 100
B = 300
↓ redo
恢复相关 Page 修改这里非常重要:redo 并不是简单只恢复“已经提交事务”的修改。
redo 首先关注的是:数据页曾经发生过哪些需要恢复的修改。
因此恢复后的状态可以先理解成接近:
A = 200 B = 300
也就是说:
事务 T1 的修改恢复出来了。
事务 T2 的页面修改也可能存在。
这没问题。
因为:下一步还有 undo。
官方的恢复流程也是先进行 redo log application,然后再处理未完成事务的回滚。
8. 第二步:回滚未完成事务
redo 应用以后,InnoDB 还需要检查:崩溃发生时,有哪些事务没有正常完成?
例如:
T1: COMMIT
应该保留。
而:
T2: ACTIVE 没有 COMMIT
就不能保留。
所以 InnoDB 根据 undo log:
B = 300
↓
undo
↓
B = 100
最终:
A = 200 B = 100
数据库重新恢复到一致状态。
MySQL 官方明确说明,InnoDB 会自动回滚崩溃时存在的未提交事务。
9. 为什么不直接只 redo 已提交事务?
你可能会想:既然最终只需要已提交事务,为什么 redo 阶段不直接判断这个 redo 属于已提交还是未提交?
因为 redo 的设计重点是:Page 级别的物理恢复。
redo 更关心的是:
某个 Page 发生过哪些修改
而事务最终:
提交 还是 回滚
属于更高层次的事务逻辑。
所以 InnoDB 把两件事分开:
redo ↓ 恢复 Page 修改 undo ↓ 处理事务回滚
这样职责非常清晰。
可以记成:
redo: 先恢复“物理现场” undo: 再清理“不应该留下的事务修改”
10. 为什么 redo 要先执行?
假设未提交事务 T2 修改了某个 Page。
这个 Page:根本还没有完整写到磁盘
但是 undo 自己需要的数据结构也属于 InnoDB 页面体系的一部分。
如果 Page 状态都还没有先恢复好:InnoDB 可能连完整的事务恢复基础都没有。
所以从整体理解上:
先通过 redo
恢复必要的数据页状态
↓
再根据事务信息和 undo
回滚未完成事务
更加合理。
这也就是为什么 Crash Recovery 不是简单的:
找到没提交事务 ↓ 直接 undo
而是需要先进行 redo recovery。
11. redo 从哪里开始恢复?
假设 MySQL 已经运行了:
30 天
redo log 中经历了海量修改。
崩溃以后难道要:从数据库创建的第一天开始把所有 redo 重放一遍?
显然不可能。
所以 InnoDB 需要一个非常重要的机制:Checkpoint
12. 什么是 Checkpoint?
Checkpoint 表示:在这个位置之前需要保证的旧修改,已经有足够的数据页状态持久化到磁盘,因此 Crash Recovery 不需要再从更早的位置重新做起。
InnoDB 会记录:
Checkpoint LSN
服务器崩溃以后:
找到最近 Checkpoint
↓
从 Checkpoint 附近开始扫描 redo
↓
应用之后需要恢复的修改
而不是:
从 redo 历史最开始的位置 一路恢复到最后
MySQL 官方明确说明,在 crash recovery 时,InnoDB 会寻找日志中的 checkpoint 标记,并从该 checkpoint 向前扫描 redo、应用之后的修改。
13. 什么是 LSN?
Log Sequence Number
可以简单理解成:redo log 中不断向前增长的位置编号。
随着 redo 不断产生:
LSN 100 LSN 200 LSN 300 LSN 400 LSN 500
不断向前增长。
例如:
Checkpoint LSN = 300 Current LSN = 500
那么 crash recovery 大致关注:
300 ↓ 400 ↓ 500
这个范围中的 redo。
MySQL 8.4 还提供了诸如 Innodb_redo_log_checkpoint_lsn、Innodb_redo_log_current_lsn、Innodb_redo_log_flushed_to_disk_lsn 等状态变量,用来表示 checkpoint、当前 redo 位置和已持久化位置。
14. Checkpoint 是把整个 Buffer Pool 一次性刷盘吗?
不是。
你可能会认为:
Checkpoint ↓ 暂停数据库 ↓ 把所有 Dirty Page 全刷盘 ↓ 继续运行
InnoDB 并不是这么做的。
它采用的是:Fuzzy Checkpoint
可以理解成:后台逐渐把 Dirty Page 分批刷盘,然后不断向前推进 Checkpoint。
官方明确说明,InnoDB 使用 fuzzy checkpoint,不需要一次性把整个 Buffer Pool 全部刷盘,而是以小批量方式持续 Flush 修改过的数据页。
所以更接近:
Dirty Page
Dirty Page
Dirty Page
Dirty Page
↓
后台逐步 Flush
↓
Checkpoint LSN
逐渐向前推进
而不是:
停止整个数据库 ↓ 一次性刷完全部脏页
15. 为什么 Checkpoint 很重要?
主要有两个原因。
第一:减少 Crash Recovery 需要扫描和重做的 redo 范围。
例如没有 Checkpoint:
redo: 1 → 1000000
崩溃以后可能需要处理大量日志。
有 Checkpoint:
Checkpoint = 950000 Current = 1000000
就只需要重点处理:
950000 → 1000000
恢复速度明显更合理。
第二:允许旧 redo 空间被重新利用。
redo log 的空间不是无限增长的。
当某些旧 redo 已经对应到:
相关 Dirty Page 已经安全刷盘
以后,这些旧日志就不再是 Crash Recovery 必需的信息。
因此对应日志空间可以被后续 redo 重用。
MySQL 的 redo 实现文档也说明,last_checkpoint_lsn 之前的 redo 已经不再需要用于恢复,可以被覆盖重用。
16. WAL、Checkpoint、Crash Recovery 是什么关系?
首先 WAL 保证:对应 Dirty Page 刷入数据文件之前,相关 redo 必须先持久化。
于是:
修改 Buffer Pool
↓
产生 Dirty Page
+
产生 redo
↓
redo 先持久化
↓
Dirty Page 后续才能安全刷盘
然后后台:
不断 Flush Dirty Page
↓
Checkpoint 向前推进
如果发生:
💥 Crash
则:
找到最近 Checkpoint
↓
扫描之后的 redo
↓
恢复需要恢复的数据页修改
所以:
WAL
↓
确保恢复信息先存在
Checkpoint
↓
确定恢复从哪里开始
redo
↓
真正重做 Page 修改
三者并不是三个孤立的知识点。
17. 一张图把正常运行和崩溃恢复连起来
正常运行:
UPDATE ↓ 修改 Buffer Pool ↓ Dirty Page + redo ↓ redo 持久化 ↓ COMMIT ↓ 后台 Flush Dirty Page ↓ Checkpoint 不断推进
突然:
Crash
重新启动:
读取 Checkpoint LSN
↓
扫描后续 redo
↓
Redo Log Application
↓
恢复 Page
↓
识别未完成事务
↓
使用 undo 回滚
↓
数据库恢复一致
这就是 InnoDB Crash Recovery 的主线。
18. 一个已经 COMMIT 的事务一定能恢复吗?
这里必须加一个很重要的前提:取决于 redo 是否真正达到了对应的持久化条件。
默认可靠配置下,事务提交时会保证相应日志持久化。
但 MySQL 存在参数:innodb_flush_log_at_trx_commit
它会影响事务提交时 redo 的写入和刷盘策略。
如果为了性能使用了更弱的持久化配置,那么:服务器突然掉电时,最近一小段已经返回 COMMIT 成功的事务仍可能丢失。
MySQL 8.4 官方文档也明确指出,弱化日志刷盘策略可能在崩溃时损失最近的事务;对于需要完整 ACID durability 的场景,应使用可靠的日志同步配置。
所以不能把 Crash Recovery 理解成:无论怎么配置,只要客户端看到 COMMIT,就绝对永远不会丢。
正确理解是:Crash Recovery 能恢复已经可靠持久化到 redo 中的信息;持久性本身还受到日志刷盘配置影响。
19. 两阶段提交在 Crash Recovery 时又怎么发挥作用?
前面讲 UPDATE 时,我们学过:
InnoDB PREPARE
↓
写 binlog
↓
InnoDB COMMIT现在考虑最危险的情况:
InnoDB PREPARE
↓
binlog 已经成功
↓
💥 Crash
↓
InnoDB 还没完成最终 COMMIT重启以后怎么办?
不能简单看到:PREPARED
就回滚。
因为 binlog 已经成功写入了这个事务。
MySQL 重启恢复时会检查 binary log 中完整记录的事务 XID,并告诉存储引擎:
prepared transaction 如果 XID 在完整 binlog 中 ↓ 提交 如果不在 ↓ 回滚
MySQL 8.4 官方文档明确说明,服务器重启时会扫描最新 binary log 中的事务标识,并让 InnoDB 完成已经成功写入 binlog 的 prepared transaction,从而保证 binlog 和 InnoDB 数据保持一致。
这样上一章的:两阶段提交
就和这一篇的:Crash Recovery
真正连起来了。
20. 所以 binlog 会参与 InnoDB 的页面恢复吗?
不会直接替代 redo。
需要区分两个职责。
redo log:恢复 InnoDB 数据页。binlog:在事务协调场景下,帮助确定某些 PREPARED 事务最终应该 COMMIT 还是 ROLLBACK。
所以:
redo ↓ Page Recovery binlog ↓ Transaction Commit Coordination
这也是为什么前面我们说:不能只留下 binlog,然后不要 redo log。
binlog 根本不是 InnoDB 的 Page 级恢复日志。
21. 那 undo log 会不会把已提交事务回滚掉?
不会。
undo log 本身只是:提供“如何撤销修改”的信息。
最终到底要不要执行 undo,要看事务状态。
例如:
事务 A: COMMITTED
那么对应修改应该保留。
不会因为存在 undo 就把它撤销。
而:
事务 B: Crash 时仍然 ACTIVE
则需要:
undo ↓ ROLLBACK
所以:undo 是撤销能力,不等于所有修改最终都会被撤销。
这和正常执行:
ROLLBACK;
是同一个道理。
22. Crash Recovery 完成前 MySQL 一直不能连接吗?
这里实际实现比我们画的简单流程更复杂。
MySQL 官方恢复流程中,redo log application 会在初始化阶段执行,并且是在接受连接之前完成。
但 redo application 之后,InnoDB 会尽可能早地开始接受连接。
例如:未完成事务的 rollback
可以由后台线程继续执行。
也就是说,实际情况可能是:
redo recovery 完成
↓
开始允许新的连接
↓
后台继续 rollback 某些旧事务
↓
后台继续 purge 等工作
因此如果崩溃前存在一个非常大的未提交事务:MySQL 虽然可能已经可以提供服务,但后台 rollback 仍然可能持续很长时间。
官方文档也指出,Crash Recovery 中对未完成事务的 rollback 可以在接受新连接后由后台线程继续执行,并且恢复中的事务在 rollback 完成前仍可能导致锁冲突。
23. 一个超大事务为什么会让恢复很慢?
假设执行:
START TRANSACTION; UPDATE huge_table SET status = 1;
一次修改了几千万行。
执行很久以后:
还没有 COMMIT
突然服务器崩溃。
重启以后:
redo ↓ 恢复必要页面
接下来发现:
这个超大事务没有提交
那么就必须:
undo ↓ 逐步回滚大量修改
显然会很慢。
MySQL 官方甚至说明,大型未完成事务的 rollback 时间可能明显长于事务之前运行的时间。
这也是为什么我们一直强调:尽量避免超长、超大的事务。
它不仅会:
- 长时间持有锁
- 保留大量 undo 历史
- 影响 MVCC
- 增加日志压力
还可能:让崩溃后的恢复和 rollback 变得非常慢。
24. redo 能解决所有磁盘写入损坏问题吗?
不能。
这里再补一个非常重要的概念:Doublewrite Buffer
假设一个 Page 默认是:16KB
现在 InnoDB 正准备把整个 Page 写入数据文件:
16KB Page
结果只写了一半:
写了 8KB ↓ 突然断电
那么这个 Page 可能变成:
Torn Page
也就是:页面本身只写了一部分,结构已经损坏。
这和普通的:
整个 Page 还是旧版本
不是一回事。
InnoDB 为这种问题提供了 Doublewrite Buffer。
25. Doublewrite Buffer 是怎么工作的?
在 Page 真正写到数据文件对应位置之前,InnoDB 会先把 Page 写到:
Doublewrite Buffer
大致:
Dirty Page
↓
先写 Doublewrite Buffer
↓
确认完成
↓
再写真正 data file
如果第二次写真正数据文件时:
写了一半 ↓ 💥 Crash
恢复时可以从 Doublewrite Buffer 找到一个完整 Page 副本进行修复。
MySQL 8.4 官方文档明确说明,doublewrite buffer 就是用来在 Page 写入中途发生系统、存储或 mysqld 崩溃时,恢复这种 incomplete page write 的。
所以:
redo log 和 Doublewrite Buffer 也不是一回事。
26. redo 和 Doublewrite 分别解决什么?
可以这样理解。
普通情况:
Page 完整 但是版本比较旧 ↓ redo 把缺失修改重做
而 Torn Page:
一个 16KB Page 只写进去一部分 ↓ Page 本身已经损坏 ↓ Doublewrite Buffer 提供完整 Page 副本
因此:
Doublewrite ↓ 先解决 Page 是否完整 redo ↓ 再解决 Page 是否包含最新需要恢复的修改
这两个机制配合,Crash Recovery 才更加可靠。
27. Crash Recovery 的完整流程怎么理解?
可以概括成:
MySQL Crash
↓
重新启动
↓
找到 Tablespace
↓
找到 Checkpoint LSN
↓
扫描 redo log
↓
Redo Log Application
↓
恢复必要的数据页修改
↓
处理 PREPARED 事务状态
↓
binlog / transaction coordinator
帮助判断最终提交或回滚
↓
回滚未完成事务
↓
undo
↓
后台继续 purge 等工作
↓
数据库恢复正常
真实 MySQL 内部还有更多步骤,例如 tablespace discovery、change buffer merge、purge 等,但我们现在最应该掌握的是:
Checkpoint
↓
Redo
↓
Transaction State
↓
Undo
这条主线。MySQL 官方列出的 Crash Recovery 过程也包括 tablespace discovery、redo log application、rollback incomplete transactions、change buffer merge 和 purge 等步骤。
28. 正常关闭为什么通常不需要这么重的恢复?
如果 MySQL 正常关闭:
正常 Shutdown
InnoDB 有机会完成必要的日志和后台处理。
因此下一次启动通常不需要像异常 Crash 那样处理大量 redo recovery。
InnoDB redo 实现文档也指出,在正常 shutdown 情况下,redo 在逻辑上通常已经不再存在需要从 checkpoint 之后重新应用的记录;而某些 fast shutdown 模式会把更多恢复工作留到下次启动。
所以:
正常关闭
和:
直接 kill / 断电
不是同一种启动恢复成本。
29. Crash Recovery 和备份恢复是一回事吗?
不是。
这个也非常重要。
Crash Recovery:数据库自己发生异常宕机以后,利用 redo、undo 等机制恢复当前数据库。
例如:
服务器突然断电 ↓ 重启 MySQL ↓ InnoDB 自动 Crash Recovery
而:Backup Recovery
或者:Point-In-Time Recovery
解决的是:数据文件真的丢了、磁盘损坏了,或者用户误删数据以后怎么办?
例如:
DROP TABLE important_table;
这是一个正常执行并提交成功的操作。
Crash Recovery 不会说:“这是误操作,我帮你撤销。”
因为从数据库角度:
事务正常执行 正常提交
它没有崩溃一致性问题。
这种情况需要:
Backup + binlog ↓ PITR
恢复。
所以:
Crash Recovery ≠ Backup Recovery
这是两套不同的问题。
30. redo log、undo log、binlog 怎么区分?
redo log 属于InnoDB
核心作用:Crash Recovery
主要解决:应该存在的 Page 修改还没完整写入数据文件怎么办?
undo log 属于InnoDB
核心作用:ROLLBACK + MVCC
在 Crash Recovery 中主要解决:未提交事务的修改已经进入数据页怎么办?
binlog 属于MySQL Server
核心作用:Replication + PITR
同时在 InnoDB 两阶段提交恢复中,还可以帮助:判断某些 PREPARED 事务最终应该提交还是回滚。
所以:
redo ↓ 重做 undo ↓ 撤销 binlog ↓ 记录事务的数据变化
三个日志各有职责。
以上就是MySQL InnoDB异常崩溃后的数据自动恢复机制的详细内容,更多关于MySQL崩溃恢复的资料请关注脚本之家其它相关文章!
相关文章
mysql出现ERROR 2003 (HY000): Can‘t connect to MySQL ser
本文主要介绍了mysql出现ERROR 2003 (HY000): Can‘t connect to MySQL server on ‘localhost‘ (10061)的解决方法,具有一定的参考价值,感兴趣的可以了解一下2025-03-03


最新评论