本文将结合MySQL 8.0.19 分析InnoDB崩溃恢复的拉起过程,包括恢复前的准备工作,redo回放,undo回滚,以及崩溃恢复后Crash Safe DDL的实现。其中重点介绍redo的回放。
整体的代码流程如下,InnoDB崩溃恢复的流程是从srv_start, innobase_dict_recover ,ha_post_recover这三个函数中展开,后文会详细介绍。
|-->mysqld_main
| |--> init_server_components
| | |--> dd::init
| | | |-->/*忽略了一些链路*/
| | | |--> run_bootstrap_thread
| | | | |--> /*恢复线程中执行*/
| | | | |--> handle_bootstrap
| | | | | |--> do_pre_checks_and_initialize_dd
| | | | | | |--> DDSE_dict_init
| | | | | | | |--> innobase_ddse_dict_init
| | | | | | | | |--> innobase_init_files
| | | | | | | | | |--> /*崩溃恢复主函数*/
| | | | | | | | | |--> srv_start
| | | | | | |--> restart_dictionary
| | | | | | | |--> bootstrap::restart
| | | | | | | | |--> DDSE_dict_recover
| | | | | | | | | |--> /*事务回滚主函数*/
| | | | | | | | | |--> innobase_dict_recover
| | | | |--> /*崩溃恢复结束*/
| | |--> /*Crash Safe DDL*/
| | |--> ha_post_recover
重要结构体
程序=算法+数据结构,所以在介绍具体实现之前,将介绍涉及到的结构体,方便理解代码的实现。
recv_sys_t : 这个结构体变量用来描述恢复系统运行时刻的状态。InnoDB运行时刻有一个该数据结构的实例recv_sys。
下面的代码块为结构体的成员变量,其中英文注释来自源码,中文注释是自己的理解。
spaces : 是以space_id做hash的hash表,表里面存放的元素是以page_no做hash的hash表(pages), pages表里存放的是按照lsn大小排序的需要在该页上进行恢复的日志记录。
parse_start_lsn:本次日志重做恢复起始的lsn,如果是从checkpoint处开始恢复,等于checkpoint_lsn。
scanned_lsn: 在恢复过程,将恢复日志从log_sys->buf解析块后存入recv_sys->buf的日志lsn.
recovered_lsn:已经将数据恢复到page中或者已经将日志操作存储addr_hash当中的日志lsn;
struct recv_sys_t {
/** Every space has its own heap and pages that belong to it. */
struct Space {
/** Constructor
@param[in,out] heap Heap to use for the log records. */
explicit Space(mem_heap_t *heap) : m_heap(heap), m_pages() {}
/** Default constructor */
Space() : m_heap(), m_pages() {}
/** Memory heap of log records and file addresses */
mem_heap_t *m_heap;
/** Pages that need to be recovered */
Pages m_pages;
};
using Pages =
std::unordered_map<page_no_t, recv_addr_t *, std::hash<page_no_t>,
std::equal_to<page_no_t>>;
/*内部维护的hash table,key为SpaceID,value为Space。
而Space内部也有个hash table,保存相同page_no的日志。
暴露给程序,经常在代码里看到*/
/** Hash table of pages, indexed by SpaceID. */
using Spaces = std::unordered_map<space_id_t, Space, std::hash<space_id_t>,
std::equal_to<space_id_t>>;
Spaces *spaces;
/*spaces中包含recv_addr的个数*/
/** Number of not processed hashed file addresses in the hash table */
ulint n_addrs;
/*!< mutex protecting the fields apply_log_recs, n_addrs, and the
state field in each recv_addr struct */
/*保护锁*/
ib_mutex_t mutex;
/** mutex coordinating flushing between recv_writer_thread and
the recovery thread. */
ib_mutex_t writer_mutex;
/*mysql封装的条件变量,用来通知page cleaner线程刷盘操作*/
/** event to activate page cleaner threads */
os_event_t flush_start;
/*mysql封装的条件变量,用来通知page cleaner线程停止刷盘*/
/** event to signal that the page cleaner has finished the request */
os_event_t flush_end;
/*刷盘方式*/
/** type of the flush request. BUF_FLUSH_LRU: flush end of LRU,
keeping free blocks. BUF_FLUSH_LIST: flush all of blocks. */
buf_flush_t flush_type;
/*正在应用log record到page中*/
/** This is true when log rec application to pages is allowed;
this flag tells the i/o-handler if it should do log record
application */
bool apply_log_recs;
/*批量应用log record标志*/
/** This is true when a log rec application batch is running */