4、数据库层面的恢复:MySQL的崩溃恢复流程、WAL机制、Redo Log与Undo Log

数据库崩溃恢复,说白了就是MySQL在意外宕机后,如何保证数据不丢、事务一致。很多DBA觉得这层有InnoDB兜底,不用太关心。但我见过太多人因为不懂这个流程,升级中断后恢复时手忙脚乱,甚至把数据搞得更糟。

今天我们就把它彻底讲透。

4.1 崩溃恢复的核心:WAL机制

WAL,全称Write-Ahead Logging,中文叫预写日志。它的核心思想就一句话:先写日志,再写数据。

你想想看,如果每次修改数据都直接刷到磁盘,那磁盘IO得多慢?而且事务提交时,万一写到一半断电了,数据就处于半残状态。WAL的做法是:事务提交时,只把修改操作记录到日志文件(Redo Log)里,保证日志落盘就算成功。真正的数据页修改,可以等后面慢慢刷。

WAL的核心原则:

  • 日志必须在数据之前持久化
  • 日志是顺序写,性能远高于随机写
  • 崩溃后通过日志重放,保证数据不丢

我个人习惯把WAL比作「记账本」。你每天花多少钱,先记在本子上,月底再统一对账。就算本子中途被撕了,只要账本还在,钱就丢不了。MySQL的Redo Log就是这个账本。

4.2 Redo Log:保证事务的持久性

Redo Log记录的是「物理修改」——哪一页、哪个偏移量、改成了什么值。它只关心结果,不关心过程。

举个例子,你执行了一条UPDATE语句:

UPDATE users SET balance = balance - 100 WHERE id = 1;

InnoDB会先把这个操作记录到Redo Log Buffer里,事务提交时,把Buffer里的日志刷到磁盘上的Redo Log文件。这时候就算数据库立刻崩溃,重启后也能通过Redo Log把balance的修改重放回来。

我记得有一次线上升级,脚本写错了导致MySQL进程被kill。重启后,业务方说数据没丢,就是靠Redo Log恢复的。嗯,那一刻觉得WAL真香。

Redo Log的写入流程

  1. 事务修改数据页,生成Redo Log记录
  2. 记录写入Redo Log Buffer(内存)
  3. 事务提交时,将Buffer中的日志刷到磁盘(fsync)
  4. 后台线程异步将脏页刷到磁盘

避坑指南:我曾经遇到过Redo Log文件太小,导致日志频繁切换,数据库性能骤降。建议生产环境把innodb_log_file_size设置为1GB以上,innodb_log_files_in_group保持默认2个。太小的话,日志写满时会触发脏页强制刷盘,造成性能抖动。

4.3 Undo Log:保证事务的原子性与MVCC

Undo Log和Redo Log正好相反。它记录的是「如何撤销」——如果事务回滚,怎么把数据改回去。

Undo Log里存的是修改前的数据快照。比如你更新了balance,Undo Log就记下「原来的balance是多少」。如果事务回滚,InnoDB就根据Undo Log把数据恢复原样。

另外,Undo Log还是MVCC(多版本并发控制)的基础。每个事务读取数据时,通过Undo Log构建出自己可见的旧版本,从而实现「读不阻塞写,写不阻塞读」。

注意:Undo Log不是无限保留的。InnoDB有后台线程(Purge Thread)负责清理已经提交且不再需要的Undo Log。如果长事务一直不提交,Undo Log会越积越多,导致undo表空间膨胀。我曾经见过一个跑了12小时的事务,把undo表空间撑到200GB,差点把磁盘打满。

4.4 崩溃恢复的完整流程

当MySQL意外崩溃后重启,InnoDB会自动进入恢复模式。整个过程分为三步:

步骤 操作 说明
1 扫描Redo Log 从最后一个检查点开始,扫描所有Redo Log记录
2 Redo阶段(前滚) 将Redo Log中的修改重新应用到数据页,保证已提交事务不丢
3 Undo阶段(回滚) 利用Undo Log回滚未提交的事务,保证原子性

为什么会分两步?因为崩溃时,可能有些事务已经提交了(Redo Log已落盘),但数据页还没刷到磁盘;也可能有些事务还没提交,但数据页已经被刷进去了。Redo阶段保证前者不丢,Undo阶段保证后者回滚。

关键点:崩溃恢复只保证「已提交的事务持久化,未提交的事务回滚」。它不会丢失数据,也不会产生脏数据。这就是ACID中的D(持久性)和A(原子性)。

4.5 检查点(Checkpoint)的作用

每次崩溃恢复都要扫描整个Redo Log,那如果日志文件很大,恢复时间不就爆炸了?

所以InnoDB引入了检查点机制。每隔一段时间,InnoDB会把内存中所有脏页刷到磁盘,并记录一个检查点位置。崩溃恢复时,只需要从检查点之后的Redo Log开始扫描,大大缩短恢复时间。

我个人习惯把检查点理解为「书签」。你看一本厚书,看到第100页夹个书签。下次再看,直接从书签开始,不用从头翻。

4.6 核心流程图

下面这张图展示了崩溃恢复的完整流程,我建议你保存下来,面试或排障时都能用上。

MySQL崩溃恢复流程图 MySQL崩溃恢复完整流程 MySQL崩溃重启 步骤1:扫描Redo Log(从最后一个检查点开始) 步骤2:Redo阶段(前滚) 步骤3:Undo阶段(回滚未提交事务) 恢复完成,数据库可用 说明: • Redo阶段:重放已提交事务的修改,保证持久性(Durability) • Undo阶段:回滚未提交事务的修改,保证原子性(Atomicity) • 检查点(Checkpoint)用于缩短恢复扫描范围,提升恢复速度

4.7 实战中的避坑指南

讲完理论,说几个我踩过的坑,希望对你有帮助。

坑1:Redo Log太小导致恢复失败

我曾经接手过一个案例,某系统升级中断后重启,结果恢复过程持续了40分钟还没结束。查了半天,发现Redo Log只有128MB,日志频繁切换,检查点跟不上,导致恢复时需要扫描大量日志。后来把innodb_log_file_size改成2GB,恢复时间降到10秒以内。

坑2:长事务阻塞Undo清理

有一次线上数据库磁盘报警,发现undo表空间涨到了150GB。排查后发现有个应用开启了事务后,一直没提交也没回滚,导致Undo Log无法清理。kill掉那个会话后,磁盘空间才慢慢释放。建议监控information_schema.innodb_trx,及时发现长事务。

坑3:升级中断后不要直接重启

如果升级过程中MySQL被中断,不要急着直接重启。先检查Redo Log和Undo Log的完整性。我习惯先备份ib_logfile和undo表空间文件,再启动数据库。万一恢复过程中出问题,还能回退到备份状态。

4.8 小结

数据库层面的恢复,核心就是WAL机制下的Redo Log和Undo Log配合。Redo Log保证已提交事务不丢,Undo Log保证未提交事务回滚。崩溃恢复时,先Redo前滚,再Undo回滚,最后数据库达到一致状态。

理解了这个流程,你就能在升级中断后,从容地判断数据库是否安全、恢复需要多久、以及如何优化恢复速度。嗯,这些知识,关键时刻能救命。