本文将结合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 */