【华为云MySQL技术专栏】MySQL8.0 InnoDB崩溃恢复流程解析
1、背景介绍
数据库系统与文件系统的核心差异,在于数据库系统能够最大限度地保证ACID特性。在ACID特性中,数据一致性尤为重要。
在崩溃恢复场景下,InnoDB引擎是通过Redo Log(重做日志,记录数据页的物理修改)和Undo Log(撤销日志,记录事务中更新前的历史数据)协同来实现数据一致性这一目标的。
当数据库异常崩溃后重启,会先触发Roll-forward(前滚),通过重放Redo Log中所有事务的物理修改,实现数据持久性。继而触发Roll-back(回滚),基于Undo Log回滚未完成的事务,同时清理崩溃时未完成事务的中间状态和临时资源。最终,将数据库恢复到崩溃前的一致性状态,确保所有已提交事务的修改已持久化,未提交事务的修改不生效。
本文将为大家详述InnoDB在崩溃恢复场景下维护数据一致性的原理。
2、Redo Log实现数据库前滚
数据库前滚恢复,主要依赖Redo Log体系。
InnoDB会首先找到最后的Checkpoint LSN,以此LSN为起点,逐条扫描后续的Redo Log。每条Redo Log均包含以下关键信息:表空间ID(space id)、页号(page no)以及物理修改记录。基于space id和page no,InnnoDB将Redo Log分发到哈希表hash_table中,保证同一个数据页的Redo Log被分发到同一个哈希桶中。扫描完后,再遍历整个哈希表,依次应用每个数据页的Redo Log,保证每个数据页将会恢复到崩溃之前的状态。
下面详细分析一下实现原理。
MySQL在启动时会默认创建两个文件:ib_logfile0和ib_logfile1,这两个文件以循环写入的方式交替记录Redo Log,并通过双Checkpoint保证写入的安全性——InnoDB在ib_logfile0的头部预留了两个Checkpoint区域,这两个字段交替更新,确保即使一个Checkpoint区域损坏,另一个仍可用。
以下是数据库前滚恢复过程中各数据结构的关系和流程图:
图1 前滚恢复中的数据结构关系图
执行完Roll-forward,整个InnoDB Recovery的第一阶段也就结束了。在该阶段中,所有的Redo Log中记录的修改都被应用到了内存的数据页上,保证了内存数据页与Redo Log中的修改记录一致,但并不会立即将页面刷盘,刷盘将由后台线程异步完成。
3、Undo Log实现数据库回滚
但仅仅通过前滚恢复是不够的,数据库崩溃的时候可能有一些没有提交的事务或者提交一半的事务,这个时候就需要重建崩溃前的事务,并依据事务的不同状态,进行回滚或者提交。这主要分为三步:
实现的流程也比较简单,Undo Log本身的修改也会被记录到Redo Log中,这意味着,即使Undo Log的物理页未及时刷盘,崩溃后仍可通过Redo Log重建Undo信息。在Redo Log应用完成后,Undo Log页也会恢复到崩溃之前的状态。随后Innodb通过trx_sys_init_at_db_start初始化事务子系统,此过程的核心是回滚段相关内存结构以及事务链表的重建。
接着会遍历回滚段的所有Undo槽(Slot),将磁盘上的Undo Log段信息加载到内存中,并构建对应的trx_undo_t对象(用于管理单个事务的Undo log)。
Undo Log主要分为两种类型:TRX_UNDO_INSERT和TRX_UNDO_UPDATE。前者主要是提供给insert操作用的,后者是给UPDATE和DELETE操作使用。
Undo Log有两种作用,事务回滚的时候用和MVCC快照读取的时候用。由于INSERT的数据不需要提供给其他线程用,所以只要事务提交,就可以删除TRX_UNDO_INSERT类型的Undo Log。TRX_UNDO_UPDATE在事务提交后还不能删除,需要保证没有快照使用它的时候,才能通过后台的Purge线程清理。
不同类型的trx_undo_t对象会被添加到不同的Undo链表中,创建出来的Undo链表有4种类型:
rseg->insert_undo_list和rseg->update_undo_list分别存储当前活跃事务中与 TRX_UNDO_INSERT类型和TRX_UNDO_UPDATE类型的Undo Log。
rseg->insert_undo_cached和rseg->update_undo_cached分别存储已提交事务中可复用的TRX_UNDO_INSERT类型和TRX_UNDO_UPDATE类型相关的Undo Log。
回滚段内存结构初始化的关键代码如下:
由于第一步中已经在内存中建立起了undo_insert_list和undo_update_list链表,所以这一步只需要遍历这两个链表,根据trx_undo_t对象重建起崩溃前的事务状态,根据Undo Log的state字段判断事务状态:
以上需要解析的事务状态包括:
如果Undo Log的状态是TRX_UNDO_ACTIVE,则事务的状态为TRX_ACTIVE,重建起事务后,将按照事务id加入到trx_sys->trx_list链表中。接着会统计所有需要回滚的事务(事务状态为TRX_ACTIVE)一共需要回滚多少行数据,输出到错误日志中,类似:
5 transaction(s) which must be rolled back or cleaned up。InnoDB: in total 342232 row operations to undo的字样。
最后,会去开启一个后台线程来做事务回滚和清理,若Binlog开关关闭,事务提交会变成单阶段提交,因此无PREPARED事务需要协调,对于处于TRX_STATE_ACTIVE状态的事务直接执行回滚操作,对于处于TRX_STATE_COMMITTED_IN_MEMORY状态的事务,由于已经是内存已提交状态,所以只需要执行释放事务对象等资源回收的收尾操作。
若Binlog开关打开,MySQL会使用两阶段提交(2PC)来保证Binlog和InnoDB的Redo Log之间的一致性。Binlog打开场景,MySQL对于TRX_STATE_ACTIVE与TRX_STATE_COMMITTED_IN_MEMORY状态的事务与Binlog关闭场景的处理方式保持一致。完成上述状态的事务处理后,理论上事务链表trx_sys->trx_list上只存在PREPARE状态的事务。而这一部分需要和Server层的Binlog联合来进行崩溃恢复。回到Server层,在初始化完了InnoDB已经其他支持XA的存储引擎后,如果Binlog打开了,我们就可以通过Binlog来进行XA恢复:
XA Recover阶段的关键流程如下:
图2 XA Recover关键流程图
4、总结
MySQL通过Redo Log和Undo Log的协同机制,在事务执行与崩溃恢复过程中来维护数据一致性:
Redo Log以物理日志形式记录数据页修改,确保未持久化的操作在崩溃后通过“前滚”恢复至崩溃前状态;已提交的事务将由Redo Log保证持久性和原子性,而未提交的事务才依赖Undo Log。事务中的每条数据变更(INSERT/DELETE/UPDATE)都会生成逆向操作的逻辑日志,记为Undo Log,Undo Log通过记录数据变更前的历史版本,确保未提交的事务可回滚到初始状态,实现“全做或者全不做”的原子性。
这两类日志在数据库常规操作中虽然对上层业务是“透明”的,但其底层协同是事务原子性、持久性的核心保障,同时为数据复制、备份及时间点恢复(PITR)提供了基础支持,最终实现在无需DBA干预下,若发生常规故障场景(进程异常终止、短暂断电等),数据库可自动恢复到崩溃前的一致性状态(满足ACID特性,已提交事务的所有修改持久化,未提交事务完全回滚,且不存在中间状态数据)。但需注意,在极端场景(如内存跳变导致错误数据写入或者数据损坏等),仅依赖日志可能依然无法完全恢复。
- 点赞
- 收藏
- 关注作者
评论(0)