Tavio's blog Tavio's blog
首页
  • JVM底层原理
  • 邪恶多线程
  • MyBatis底层原理
  • Spring底层原理
  • MySQL的优化之路
  • ClickHouse的高性能
  • Redis的快速查询
  • RabbitMQ的生产
  • Kafka的高吞吐量
  • ES的入门到入坑
  • MySQL自增ID主键空洞
  • 前端实现长整型排序
  • MySQL无感换表
  • Redis延时双删
  • 高并发秒杀优惠卷
  • AOP无侵入式告警
  • 长短链接跳转
  • 订单超时取消
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Tavio Zhang

努力学习的小码喽
首页
  • JVM底层原理
  • 邪恶多线程
  • MyBatis底层原理
  • Spring底层原理
  • MySQL的优化之路
  • ClickHouse的高性能
  • Redis的快速查询
  • RabbitMQ的生产
  • Kafka的高吞吐量
  • ES的入门到入坑
  • MySQL自增ID主键空洞
  • 前端实现长整型排序
  • MySQL无感换表
  • Redis延时双删
  • 高并发秒杀优惠卷
  • AOP无侵入式告警
  • 长短链接跳转
  • 订单超时取消
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • MySQL InnoDB
  • MySQL缓冲池与WAL机制
  • MySQL索引底层
  • MySQL事务与MVCC
  • MySQL锁机制
  • MySQL Binlog
  • MySQL崩溃恢复
    • 一、崩溃恢复的核心目标:坚守ACID中的A与D
    • 二、Redo Log:保障持久性的“重做日志”
      • 2.1 Redo Log的核心特性
      • 2.2 Redo Log的结构与LSN(日志序列号)
      • 2.3 Redo Log的写入机制与刷盘策略
      • 2.3.1 写入流程
      • 2.3.2 刷盘策略(核心参数:innodbflushlogattrx_commit)
    • 三、Undo Log:保障原子性的“回滚日志”
      • 3.1 Undo Log的核心特性
      • 3.2 Undo Log的类型与结构
      • 3.2.1 INSERT Undo Log
      • 3.2.2 UPDATE Undo Log
      • 3.3 Undo Log的生命周期
    • 四、核心协同逻辑:Redo Log与Undo Log如何配合保障一致性?
      • 案例:事务执行与崩溃恢复的协同过程
      • 1. 事务执行时的日志写入(协同前置)
      • 2. 崩溃恢复时的协同流程
      • 关键关联点:LSN与事务状态的联动
    • 五、InnoDB崩溃恢复的完整流程(四阶段)
      • 阶段1:初始化阶段(准备环境)
      • 阶段2:Redo阶段(前滚所有已记录的变更)
      • 阶段3:Undo阶段(回滚未提交事务)
      • 阶段4:清理阶段(Purge无用日志)
    • 六、生产环境优化:崩溃恢复性能与稳定性提升
      • 6.1 核心参数优化
      • 6.2 业务实践优化
      • 6.3 常见问题排查
      • 6.3.1 崩溃恢复时间过长
      • 6.3.2 Undo Log膨胀导致磁盘空间不足
    • 七、总结
  • MySQL查询优化
  • 《MySQL》笔记
Tavio
2022-09-11
目录

MySQL崩溃恢复

# 深度解析InnoDB崩溃恢复:Redo Log与Undo Log的协同机制

在数据库运行过程中,崩溃(如服务器断电、MySQL进程异常终止)是无法完全避免的。对于支持事务的InnoDB存储引擎而言,崩溃恢复是保障数据一致性的核心能力——它需要确保“已提交的事务数据不丢失(持久性)”和“未提交的事务数据不残留(原子性)”。而这一切的实现,都依赖于InnoDB的两大核心日志:Redo Log(重做日志)与Undo Log(回滚日志)。

# 一、崩溃恢复的核心目标:坚守ACID中的A与D

InnoDB作为事务型存储引擎,核心是保障事务的ACID特性。其中,崩溃恢复机制主要针对原子性(Atomicity) 和持久性(Durability):

  • 原子性保障:事务是不可分割的最小单位,要么全部执行,要么全部不执行。若事务执行过程中数据库崩溃,未提交的事务必须被“回滚”,不能残留任何数据变更。
  • 持久性保障:事务提交后,其修改的数据必须永久保存到磁盘,即使后续数据库崩溃,已提交的数据也不能丢失。

这里存在一个核心矛盾:若为了持久性,每次事务提交都将数据直接刷写到磁盘,会因磁盘IO速度慢导致性能极差;若为了性能,先将数据缓存在内存(Buffer Pool),又会面临崩溃时内存数据丢失的风险。Redo Log与Undo Log的协同设计,正是为了在“高性能”与“数据一致性”之间找到平衡——Redo Log保障持久性,Undo Log保障原子性,两者配合完成崩溃后的精准恢复。

# 二、Redo Log:保障持久性的“重做日志”

Redo Log(重做日志)是InnoDB的物理日志,记录的是“数据页的物理变更”(如“将数据页1234中偏移量56的字段值从10修改为20”),而非SQL语句的逻辑含义。其核心作用是:当数据库崩溃时,通过重放Redo Log中的物理变更,恢复所有已提交事务的修改,保障持久性。

# 2.1 Redo Log的核心特性

  • 物理日志:直接记录数据页的磁盘地址和字节级变更,重放时无需解析SQL,直接操作磁盘数据页,恢复速度快。
  • 循环写入:Redo Log以固定大小的文件组形式存储(默认包含ib_logfile0、ib_logfile1两个文件),文件写满后会循环覆盖旧日志(前提是旧日志对应的事务已提交且数据已刷盘)。
  • 顺序IO:Redo Log的写入是严格顺序的(仅追加写入),而数据页的修改是随机IO。顺序IO的速度远快于随机IO,这也是InnoDB高性能的关键原因之一。
  • 预写日志(WAL)原则:InnoDB遵循“Write-Ahead Logging”原则,即先写Redo Log,再修改内存数据页。只有当Redo Log写入完成后,才会更新Buffer Pool中的数据页,确保即使崩溃,也能通过Redo Log恢复数据。

# 2.2 Redo Log的结构与LSN(日志序列号)

Redo Log的核心索引是LSN(Log Sequence Number,日志序列号),LSN是一个单调递增的64位整数,每写入一条Redo Log,LSN就会递增,用于标记日志的先后顺序。同时,LSN还关联了数据页与Undo Log,是崩溃恢复时的“核心坐标”:

  • 每个Redo Log记录都包含当前的LSN(日志LSN)。
  • 每个数据页的头部都存储着“最后一次修改该页的LSN”(页LSN)。
  • 每个Undo Log记录也包含对应的LSN,用于关联Redo Log。

Redo Log记录的核心结构(简化):

| 日志类型(如INSERT/UPDATE) | 表空间ID | 数据页号 | 页内偏移量 | 变更后的数据 | LSN | 事务ID |
1

通过表空间ID、数据页号、页内偏移量,InnoDB能精准定位到需要修改的磁盘位置,重放时直接覆盖对应字节,效率极高。

# 2.3 Redo Log的写入机制与刷盘策略

Redo Log的写入流程分为“内存缓冲”和“磁盘持久化”两个阶段,核心依赖innodb_log_buffer_size(日志缓冲区大小)和innodb_flush_log_at_trx_commit(刷盘策略参数),具体流程如下:

# 2.3.1 写入流程

  1. 事务执行数据变更时,先将变更对应的Redo Log写入Redo Log Buffer(内存缓冲区)。
  2. 根据刷盘策略,将Redo Log Buffer中的内容刷写到磁盘的Redo Log文件中。
  3. 刷盘完成后,再更新Buffer Pool中的数据页(脏页)。

# 2.3.2 刷盘策略(核心参数:innodb_flush_log_at_trx_commit)

该参数直接决定Redo Log的刷盘时机,影响“持久性”与“性能”的平衡,是生产环境的核心配置:

  • innodb_flush_log_at_trx_commit=0:异步刷盘。事务提交时,不主动刷盘,仅将Redo Log写入Redo Log Buffer;MySQL后台线程每1秒将Buffer中的日志刷写到磁盘。优点:性能最好;缺点:安全性最低——崩溃时可能丢失1秒内所有已提交事务的Redo Log。
  • innodb_flush_log_at_trx_commit=1:同步刷盘(默认值)。每次事务提交时,都将Redo Log Buffer中的当前事务日志强制刷写到磁盘(调用fsync())。优点:安全性最高,确保已提交事务的Redo Log不丢失;缺点:性能最差(频繁fsync()消耗IO资源)。生产环境核心业务首选此配置。
  • innodb_flush_log_at_trx_commit=2:半同步刷盘。事务提交时,将Redo Log写入操作系统缓存(OS Cache),不强制刷盘;操作系统每1秒将OS Cache中的日志刷写到磁盘。优点:性能优于1,安全性优于0;缺点:崩溃时可能丢失OS Cache中的日志(如服务器断电,OS Cache数据丢失),仅适用于非核心业务。

补充:即使未提交事务,Redo Log也可能被刷盘(如Redo Log Buffer满、后台线程定期刷盘),但这不会影响数据一致性——未提交事务的Redo Log会在崩溃恢复时被Undo Log回滚。

# 三、Undo Log:保障原子性的“回滚日志”

Undo Log(回滚日志)是InnoDB的逻辑日志,记录的是“事务执行前的数据状态”(如“UPDATE user SET age=26 WHERE id=1”的Undo Log记录“id=1的age原值为25”)。其核心作用是:

  • 事务回滚:当事务执行ROLLBACK,或崩溃后需要回滚未提交事务时,通过Undo Log恢复数据到事务执行前的状态。
  • MVCC(多版本并发控制):Undo Log会保留数据的历史版本,供其他事务读取(如READ COMMITTED、REPEATABLE READ隔离级别下的快照读)。

# 3.1 Undo Log的核心特性

  • 逻辑日志:记录的是SQL操作的逆操作(如INSERT的逆操作是DELETE,UPDATE的逆操作是恢复原值),而非物理数据变更。
  • 事务私有:每个事务都有专属的Undo Log,存储在Undo段(Undo Segment)中,Undo段位于共享表空间(ibdata1)或独立表空间(.ibd文件,MySQL 5.6+支持)。
  • 可重用性:当事务提交后,其Undo Log不会立即删除,而是标记为“可重用”——只有当所有依赖该Undo Log的快照读事务结束后,Undo Log才会被清理(由Purge线程负责)。
  • 依赖Redo Log保障持久性:Undo Log本身的写入也遵循WAL原则,先将Undo Log的变更写入Redo Log,再写入Undo段。确保即使崩溃,Undo Log本身也能通过Redo Log恢复。

# 3.2 Undo Log的类型与结构

根据事务操作类型,Undo Log分为两种核心类型:

# 3.2.1 INSERT Undo Log

对应INSERT操作的回滚日志。由于INSERT的记录是事务专属的(其他事务无法读取未提交的INSERT记录),因此事务提交后,INSERT Undo Log可立即被标记为可重用,无需等待Purge线程清理。

结构简化:

| 日志类型(INSERT_UNDO) | 事务ID | 表ID | 插入记录的主键值 | LSN |
1

# 3.2.2 UPDATE Undo Log

对应UPDATE/DELETE操作的回滚日志。这类操作会修改已存在的数据,其Undo Log需要保留数据的历史版本(供MVCC读取),因此事务提交后,需等待所有依赖该版本的事务结束,才能被Purge线程清理。

结构简化(以UPDATE为例):

| 日志类型(UPDATE_UNDO) | 事务ID | 表ID | 记录主键值 | 被修改字段的原值(如age=25) | 上一个Undo Log的指针(形成版本链) | LSN |
1

其中,“上一个Undo Log的指针”会形成一条“版本链”,MVCC通过遍历版本链获取数据的历史版本。

# 3.3 Undo Log的生命周期

  1. 事务启动时,InnoDB为其分配专属的Undo段。
  2. 事务执行INSERT/UPDATE/DELETE操作时,实时生成对应的Undo Log,写入Undo段(遵循WAL原则,先写Redo Log)。
  3. 事务回滚时,InnoDB读取Undo Log,执行逆操作(如DELETE对应INSERT,UPDATE对应恢复原值),恢复数据。
  4. 事务提交时,InnoDB标记Undo Log为“可重用”:
    • INSERT Undo Log:直接标记可重用,后续可被新事务覆盖。
    • UPDATE Undo Log:标记为可重用,同时记录到“历史版本列表”,等待Purge线程清理(需确保无快照读事务依赖)。
  5. Purge线程定期扫描“历史版本列表”,清理无依赖的Undo Log,释放磁盘空间。

# 四、核心协同逻辑:Redo Log与Undo Log如何配合保障一致性?

Redo Log保障“已提交事务不丢失”,Undo Log保障“未提交事务不残留”,两者通过LSN和事务ID关联,在崩溃恢复时形成“先重做、后回滚”的协同流程。核心逻辑可总结为:

💡 崩溃恢复的核心原则:先通过Redo Log重放所有已记录的物理变更(无论事务是否提交),将数据库恢复到崩溃前的内存状态;再通过Undo Log回滚所有未提交的事务,最终保障数据一致性。

两者的协同关系可通过一个具体事务案例理解:

# 案例:事务执行与崩溃恢复的协同过程

假设存在事务T1,执行“UPDATE user SET age=26 WHERE id=1”(原age=25),执行过程中数据库崩溃,具体流程如下:

# 1. 事务执行时的日志写入(协同前置)

  1. T1启动,InnoDB分配事务ID=123,分配专属Undo段。
  2. T1执行UPDATE操作:
    • 生成UPDATE Undo Log,记录“id=1,age原值=25,事务ID=123,LSN=1000”。
    • 遵循WAL原则,先将Undo Log的变更写入Redo Log(LSN=1000),再将Undo Log写入Undo段。
    • 生成Redo Log记录,记录“表空间ID=1,数据页号=456,偏移量=78,age从25改为26,事务ID=123,LSN=1001”。
    • 将Redo Log(LSN=1001)写入Redo Log Buffer,若配置innodb_flush_log_at_trx_commit=1,则立即刷盘。
    • 更新Buffer Pool中数据页456的age值为26,数据页的页LSN更新为1001。
  3. 若T1未提交时数据库崩溃(如断电),此时Redo Log已刷盘(LSN=1000~1001),但数据页未刷盘(仍为age=25)。

# 2. 崩溃恢复时的协同流程

  1. Redo Log重放(前滚):InnoDB扫描Redo Log,发现LSN=1000~1001的日志,根据日志中的物理地址,将数据页456的age修改为26(此时数据页状态与崩溃前内存状态一致,包含未提交的T1变更)。
  2. Undo Log回滚(撤销未提交事务):InnoDB通过事务ID识别到T1未提交,读取其Undo Log(LSN=1000),执行逆操作(将age恢复为25),并生成对应的Redo Log(标记回滚操作,LSN=1002)。
  3. 最终,数据页恢复为崩溃前的原始状态(age=25),未提交的T1变更被完全回滚,已提交事务(若有)的变更通过Redo Log保留。

# 关键关联点:LSN与事务状态的联动

崩溃恢复时,InnoDB需要通过“LSN+事务状态”判断哪些日志需要重放、哪些需要回滚:

  • 事务状态记录:InnoDB在系统表空间(ibdata1)中记录所有事务的状态(提交/未提交/正在执行)。
  • LSN范围匹配:对于已提交事务,其对应的Redo Log需要完整重放(确保持久性);对于未提交事务,其对应的Redo Log即使已刷盘,也需要通过Undo Log回滚(确保原子性)。
  • 数据页LSN校验:重放Redo Log时,若日志LSN小于数据页的页LSN,说明该日志已被应用到数据页,无需重复重放;若日志LSN大于页LSN,则需要重放。

# 五、InnoDB崩溃恢复的完整流程(四阶段)

当MySQL重启后,InnoDB会自动触发崩溃恢复流程,分为“初始化阶段→Redo阶段(前滚)→Undo阶段(回滚)→清理阶段”四个核心步骤,每个步骤的具体操作如下:

# 阶段1:初始化阶段(准备环境)

  1. 加载InnoDB配置参数(如innodb_log_file_size、innodb_data_file_path等)。
  2. 初始化Buffer Pool、日志缓冲区等内存结构。
  3. 打开Redo Log文件组,读取Redo Log的起始LSN和末尾LSN,确定需要扫描的日志范围。
  4. 读取系统表空间中的事务状态信息(trx_sys),获取所有未提交事务的列表。

# 阶段2:Redo阶段(前滚所有已记录的变更)

核心目标:将数据库恢复到崩溃前的内存状态,包括已提交和未提交事务的变更。

  1. 确定重放范围:从“ checkpoint LSN ”开始,到Redo Log的末尾LSN结束。Checkpoint LSN是指“已将Redo Log对应的变更刷写到数据页的最大LSN”——小于checkpoint LSN的日志已无需重放(数据已持久化)。
  2. 逐句重放Redo Log:
    • 对于每条Redo Log,解析其表空间ID、数据页号、偏移量和变更数据。
    • 将对应数据页加载到Buffer Pool(若未加载)。
    • 校验日志LSN与页LSN:若日志LSN > 页LSN,则应用变更,更新页LSN;否则跳过。
  3. 重放完成后,Buffer Pool中的数据状态与崩溃前完全一致。

# 阶段3:Undo阶段(回滚未提交事务)

核心目标:通过Undo Log回滚所有未提交的事务,保障原子性。

  1. 遍历未提交事务列表:从阶段1获取的未提交事务中,逐个处理。
  2. 定位事务的Undo Log:通过事务ID找到其对应的Undo段和Undo Log记录(依赖LSN关联)。
  3. 执行回滚操作:
    • 读取Undo Log中的逆操作指令,在Buffer Pool中恢复数据(如UPDATE回滚为原值,INSERT回滚为删除)。
    • 为回滚操作生成新的Redo Log(标记为“回滚日志”),确保回滚操作本身的持久性。
  4. 标记事务为“已回滚”:回滚完成后,更新事务状态,释放相关资源。

# 阶段4:清理阶段(Purge无用日志)

  1. 启动Purge线程,清理已提交事务的Undo Log(尤其是UPDATE Undo Log)。
  2. 检查Redo Log的checkpoint LSN,将已持久化的Redo Log标记为可覆盖。
  3. 优化Buffer Pool:将未刷盘的脏页(已提交事务的变更)加入刷盘队列,由后台线程异步刷写到磁盘。

当四个阶段完成后,MySQL正式对外提供服务,此时数据库中的数据完全符合ACID特性:已提交事务的变更全部保留,未提交事务的变更完全回滚。

# 六、生产环境优化:崩溃恢复性能与稳定性提升

崩溃恢复的性能和稳定性,直接影响数据库崩溃后的恢复时间和业务可用性。结合前面的原理,可通过以下配置和实践优化:

# 6.1 核心参数优化

  • innodb_flush_log_at_trx_commit=1:核心业务必须设置为1,确保已提交事务的Redo Log不丢失,避免崩溃后数据丢失。
  • innodb_log_buffer_size:默认16MB,若业务存在大量大事务,可适当增大(如64MB~128MB),减少Redo Log的刷盘次数,提升性能。
  • innodb_log_file_size:Redo Log文件大小,默认48MB。增大该值(如1GB~4GB)可减少日志切换次数,提升崩溃恢复速度(重放日志范围更小);但需注意:文件越大,崩溃恢复时重放时间越长,需平衡“日常性能”与“恢复时间”。
  • innodb_log_files_in_group:Redo Log文件组数量,默认2个。建议保持默认,过多文件会增加管理开销。
  • innodb_purge_threads:Purge线程数量,默认4个(MySQL 8.0)。若存在大量UPDATE/DELETE事务,可适当增大(如8个),加快Undo Log清理速度,避免Undo段膨胀。

# 6.2 业务实践优化

  • 避免长事务:长事务会长期占用Undo段,导致Undo Log膨胀,同时增加崩溃恢复时的回滚时间(需回滚大量未提交事务)。建议拆分长事务为小事务,及时提交/回滚。
  • 控制事务并发量:过高的并发事务会导致Redo Log写入频繁,增加刷盘压力;同时,崩溃恢复时未提交事务数量增多,回滚时间变长。可通过连接池限制并发量。
  • 定期备份与监控:
    • 定期备份数据库(如每日全量+增量备份),避免极端情况下崩溃恢复失败。
    • 监控Redo Log使用情况(通过SHOW ENGINE INNODB STATUS查看LSN增长、checkpoint进度)。
    • 监控Undo段大小,若Undo段异常膨胀,需检查是否存在未提交的长事务或Purge线程异常。
  • 合理设置表空间:将大表的Undo Log存储在独立表空间(开启innodb_file_per_table),避免共享表空间(ibdata1)因Undo Log膨胀而过大。

# 6.3 常见问题排查

# 6.3.1 崩溃恢复时间过长

原因:Redo Log文件过大、未提交事务过多、Undo Log膨胀。排查方法:

-- 查看Redo Log相关信息
SHOW ENGINE INNODB STATUS;  -- 查看LSN、checkpoint信息
-- 查看未提交事务(崩溃前)
SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;  -- 若恢复后仍有未提交事务,需手动回滚
-- 检查Undo段大小
SELECT TABLE_NAME, DATA_FREE FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA='mysql' AND TABLE_NAME LIKE 'innodb_undo%';
1
2
3
4
5
6

解决方案:减小innodb_log_file_size、避免长事务、优化Purge线程配置。

# 6.3.2 Undo Log膨胀导致磁盘空间不足

原因:大量未提交事务、Purge线程清理不及时、MVCC快照读事务长期未结束。解决方案:

  • 终止长期未提交的事务(通过KILL语句)。
  • 增大innodb_purge_threads,加快清理速度。
  • 优化MVCC读事务,避免长期占用快照(如减少长查询)。

# 七、总结

InnoDB崩溃恢复的核心是“Redo Log+Undo Log”的协同机制,其底层逻辑可总结为:

  1. Redo Log是物理日志,遵循WAL原则,通过顺序IO保障持久性,崩溃后重放所有物理变更,将数据库恢复到崩溃前的内存状态。
  2. Undo Log是逻辑日志,记录数据的历史状态,保障原子性,崩溃后回滚所有未提交事务,清除无效变更。
  3. LSN是两者协同的核心纽带,通过LSN关联Redo Log、数据页、Undo Log,确保恢复时的顺序性和准确性。
  4. 崩溃恢复流程遵循“先重做(Redo)、后回滚(Undo)”的原则,最终实现数据一致性。
编辑 (opens new window)
#MySQL崩溃恢复
上次更新: 2026/01/21, 19:29:14
MySQL Binlog
MySQL查询优化

← MySQL Binlog MySQL查询优化→

最近更新
01
订单超时取消
01-21
02
双 Token 登录
01-21
03
长短链接跳转
01-21
更多文章>
Theme by Vdoing
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式