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的写入流程
- 事务修改数据页,生成Redo Log记录
- 记录写入Redo Log Buffer(内存)
- 事务提交时,将Buffer中的日志刷到磁盘(fsync)
- 后台线程异步将脏页刷到磁盘
避坑指南:我曾经遇到过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 核心流程图
下面这张图展示了崩溃恢复的完整流程,我建议你保存下来,面试或排障时都能用上。
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回滚,最后数据库达到一致状态。
理解了这个流程,你就能在升级中断后,从容地判断数据库是否安全、恢复需要多久、以及如何优化恢复速度。嗯,这些知识,关键时刻能救命。