
大家好我是数据库小学妹 生产环境最怕一个问题数据库突然宕机已提交的数据会不会丢早期我也不确定。直到我把InnoDB的崩溃恢复机制从头到尾理了一遍才真正放下心来。今天我把这个机制拆开讲。从WAL到checkpoint从前滚到回滚用实验模拟一次崩溃看看InnoDB怎么把数据救回来。如果你对数据库的可靠性有疑虑这篇能给你答案。崩溃恢复的前提WAL机制WAL全称Write-Ahead Logging。预写式日志。核心思想就一句话数据页落盘前redo日志必须先写。为什么顺序写快随机写慢。先写redo再慢慢刷页。写到一半崩了也不怕日志还在就能重做。MySQL宕机数据怎么恢复答案就在这个机制里。InnoDB的redo log是一组循环使用的文件。默认两个每个1GBib_logfile0 ← 写满后切换到 ib_logfile1 ← 写满后回到 ib_logfile0循环覆盖redo日志记录了什么每个物理数据页的修改。不是SQL语句不是逻辑操作是底层的把第X页第Y行的Z字段从旧值改成新值这种级别的记录。-- 执行一条UPDATE UPDATE orders SET amount 500 WHERE order_id 1; -- redo log中记录的是类似这样的内容 -- [LSN:12345678] Page:3, Offset:256, Change: amount字段 200→500LSN是Log Sequence Number日志序列号。每次修改递增。崩溃恢复时按LSN顺序重放。我搞错过一个概念以为redo log记的是SQL语句。其实它记的是物理页的字节级变化。这就是为什么redo log文件二进制不可读。它不是文本日志是数据页的修改痕迹。redo log的刷盘策略由innodb_flush_log_at_trx_commit控制。三个值三种安全等级值行为性能安全性0每秒刷一次最快崩溃可能丢1秒数据1每次提交刷盘最慢不丢数据推荐2每次提交写OS缓存中等可能丢操作系统级崩溃生产环境必须设成1。这是默认值但有人为了性能改成0。一旦崩溃丢失的就是那些提交了但没刷盘的事务。checkpointInnoDB的存档点如果每次崩溃都要从第一条redo log开始重放恢复时间会很长。所以InnoDB引入了checkpoint机制。简单说它就是一个存档点。checkpoint时刻之前所有已提交事务的数据页已经刷到磁盘。checkpoint之后的修改还在redo log里等着重放。下次恢复时只需从checkpoint开始重放不用从头来。checkpoint和Buffer Pool直接相关。InnoDB的脏页都在Buffer Pool里。fuzzy checkpoint刷脏时不是全量刷而是分批刷。只有当某个LSN之前的所有脏页都落盘后checkpoint才会推进到那个位置。这个推进速度决定了恢复时需要重放的redo log量。checkpoint分两种Sharp checkpoint。正常关闭数据库时触发。InnoDB把所有脏页刷盘然后记一个checkpoint。下次启动时直接跳到这个checkpoint不需要做恢复。因为所有数据都已经在磁盘上了。Fuzzy checkpoint。运行期间异步触发。不会暂停数据库而是在后台慢慢刷脏页。刷完一批checkpoint位置就往前推进一段。-- 查看checkpoint相关状态SHOWENGINEINNODBSTATUS\G-- 输出中的关键信息-- Log sequence number 12345678 ← 当前LSN-- Log flushed up to 12345670 ← 已刷盘的LSN-- Pages flushed up to 12345000 ← checkpoint对应的LSN-- Last checkpoint at 12345000 ← 最近的checkpoint位置-- 恢复进度怎么看盯error log。-- MySQL启动时会输出-- Doing recovery: scanned up to log sequence number XXX-- 看到这个数字在不断推进说明恢复在进行中。12345678减去12345000等于678。这意味着有678个单位的redo log需要在恢复时重放。checkpoint越近恢复越快。所以InnoDB会尽可能频繁地做fuzzy checkpoint。崩溃恢复的两个阶段前滚 回滚数据库宕机后重启InnoDB自动进入崩溃恢复流程。这个过程不需要人工干预。第一阶段前滚Redo。从最近的checkpoint开始把redo log里的操作重新做一遍。已提交但还没刷盘的修改重新应用到数据页。崩溃时刻 ↓ [checkpoint] ──────── [已提交事务A] ── [已提交事务B] ── [未提交事务C] ✓ 重做 ✓ 重做 ✗ 跳过前滚的结果是把所有已提交事务的修改恢复到数据页。未提交的事务也会被前滚因为redo log不分提交状态但它们会在第二阶段被回滚。第二阶段回滚Undo。前滚完成后InnoDB扫描undo log找出前滚阶段被恢复但未提交的事务把它们回滚。前滚完成后 事务A已提交 ✓ → 保留 事务B已提交 ✓ → 保留 事务C未提交 ✗ → 回滚用undo log撤销整个过程自动化。你只需要等。大事务回滚可能很慢因为undo是单线程的。-- 查看回滚进度MySQL 5.7SELECTr.trx_idASrolling_back_trx_id,r.trx_started,r.trx_queryASrolling_back_query,TIMESTAMPDIFF(SECOND,r.trx_started,NOW())ASrunning_secondsFROMinformation_schema.INNODB_TRX r;实验模拟一次崩溃恢复光说不练不踏实。我来模拟一次真实的崩溃恢复。准备环境。创建一个测试表插入数据然后强制杀进程模拟崩溃。-- 创建测试表CREATETABLEcrash_test(idINTPRIMARYKEYAUTO_INCREMENT,dataVARCHAR(100),created_atTIMESTAMPDEFAULTCURRENT_TIMESTAMP)ENGINEInnoDB;-- 插入测试数据INSERTINTOcrash_test(data)VALUES(崩溃前的数据);INSERTINTOcrash_test(data)VALUES(这条也应该在);COMMIT;-- 再插入一条但不提交STARTTRANSACTION;INSERTINTOcrash_test(data)VALUES(这条不该在);-- 不COMMIT直接杀进程# 强制杀掉MySQL进程模拟崩溃kill-9$(cat/var/run/mysqld/mysqld.pid)# 重启MySQLsystemctl start mysqld验证结果。重启后查数据SELECT*FROMcrash_test;-----------------------------------------|id|data|created_at|-----------------------------------------|1|崩溃前的数据|2026-07-1010:00:00||2|这条也应该在|2026-07-1010:00:01|-----------------------------------------前两条数据回来了。第三条未提交的那条不在了。这就是崩溃恢复的效果已提交的不丢未提交的不留。看恢复日志。启动时的error log记录了恢复过程[Note] InnoDB: Database was not shutdown normally! ← 检测到非正常关闭 [Note] InnoDB: Starting crash recovery. ← 开始崩溃恢复 [Note] InnoDB: Reading tablespace information... ← 读取表空间信息 [Note] InnoDB: Doing recovery: scanned up to LSN 12345678 ← 前滚扫描redo log [Note] InnoDB: Starting an apply batch of log records... ← 开始批量重放 [Note] InnoDB: Apply batch completed ← 前滚完成 [Note] InnoDB: Scanning undo logs... ← 扫描undo log [Note] InnoDB: Rollback of non-committed transactions completed ← 回滚完成 [Note] InnoDB: Crash recovery completed. ← 恢复完成如果数据文件坏了怎么办redo log只能恢复逻辑上已提交但没刷盘的数据。它不能修复物理损坏。如果磁盘故障导致.ibd文件损坏redo log也无能为力。这时候需要备份恢复。PITRPoint-in-Time Recovery用binlog做基于时间点的恢复那是另一套机制。崩溃恢复要多久取决于checkpoint位置。checkpoint越近重放的日志越少恢复越快。我搞错过一次概念。以为WAL能解决所有数据丢失问题。其实WAL只管崩溃恢复不管硬件故障。两者是不同的场景。崩溃恢复是所有关系型数据库的标配。底层原理都一样WAL checkpoint 前滚回滚。只是名字不同。MySQL叫redo log。Oracle叫redo log加archive log。SQL Server叫transaction log。国产数据库也遵循这套机制只是实现细节各有差异。比如金仓数据库的崩溃恢复同样基于WAL日志。恢复过程也是前滚加回滚。不同的是金仓支持指定恢复目标时间点通过配置文件设置恢复目标和WAL路径就能精确恢复到故障前的某个时刻。这在做误操作回滚时特别有用。但不管名字怎么变核心逻辑不变。日志先行检查点推进崩溃后重做已提交、撤销未提交。崩溃恢复避坑清单innodb_flush_log_at_trx_commit别改成0。有人为了性能调成0说吞吐量提升30%。没错吞吐量是提升了。但崩溃时丢的是最后一秒内所有已提交事务。你确定你的业务能承受我见过电商系统因为这个问题丢了上百笔订单事后对账对到凌晨。redo log文件大小和数量要合理配置。默认两个1GB文件。如果业务写入量大redo log很快写满触发checkpoint性能会抖动。适当增大文件比如2GB×3个减少checkpoint频率。但别太大恢复时间会变长。大事务回滚很慢要有耐心。一个跑了两个小时的事务崩溃后回滚可能也要两个小时。undo是单线程执行的。这时候千万别再杀进程否则回滚中断下次启动又要重新回滚无限循环。正确的做法是等它回滚完监控information_schema.INNODB_TRX看进度。恢复总结崩溃恢复是数据库可靠性的最后一道防线。InnoDB用WAL checkpoint 两阶段恢复保证已提交的事务不丢未提交的事务不留。日常运维能做的是确保redo log配置合理flush策略设成1定期做备份演练。别等真崩了才发现恢复不了。对数据库来说崩溃不是最可怕的。最可怕的是你不知道崩溃后能恢复到什么程度。朋友你的数据库做过崩溃恢复演练吗redo log配置是多少欢迎在评论区聊聊。我是数据库小学妹咱们下篇见