MySQL · 引擎特性 · MySQL5.7 崩溃恢复优化

在MySQL5.7之前的版本中, InnoDB每次做crash recovery之前都需要扫描数据目录,打开每个文件并创建内存对象。当目录下文件个数特别多时,会严重影响到崩溃恢复的速度。

为了解决这个问题,MySQL5.7通过结合checkpoint + 标注被修改的文件的方式,从一个checkpoint点开始,可以找到所有崩溃恢复需要打开的文件,从而避免扫描数据目录。

本文简单的记录了相关的代码,以及一个相关的优化点。

提交mini transaction

入口函数:

mtr_commit -->    
    mtr_t::Command::execute
        mtr_t::Command::prepare_write()

// fil_names_write_if_was_clean

    /// 如果space->max_lsn 等于0,表示从最近一次checkpoint开始至今,这是第一次被修改
    
    /// fil_names_dirty_and_write: 
        1. 加入到链表fil_system->named_spaces的尾部
        2. 更新fil_space_t::max_lsn为当前LSN
        3. 写入一条MLOG_FILE_NAME,日志内容包括space id 及表空间文件路径

// 如果是从上次checkpoint后第一次修改该tablespace, fil_names_dirty_and_write返回true,表示一条 MLOG_FILE_NAME已经追加到当前mtr了,因此在日志组的尾部增加"MLOG_MULTI_REC_END"

// 否则,如果从上次checkpoint之后不止一次修改,则继续走之前的逻辑:单个rec,对第一个字节设置 MLOG_SINGLE_REC_FLAG的flag,或者多个rec的话追加一字节的MLOG_MULTI_REC_END,来标记日志组的结尾位置

在prepare_write中保证了从上次checkpoint后第一次修改的tablespace都有一个MLOG_FILE_NAME写到了日志组中. 随后调用mtr_t::Command::finish_write将日志拷贝到公共buffer中.

Make Checkpoint

入口函数: log_checkpoint

这里会在持有log_sys->mutex的情况下,在做checkpoint前追加MLOG_CHECKPOINT (但如果是一次clean的shutdown,则无需此步骤,因为已经保证了checkpoint后没有新的日志写入)

// fil_names_clear

/// 遍历fil_system->named_spaces链表:
1. 如果fil_space_t::max_lsn小于请求checkpoint的LSN(通常是BufferPool中最老的脏block的LSN),则清空fil_space_t::max_lsn为0,并从链表移除, 这样从当前位置开始,如果下次checkpoint之前这个表都没修改的话,就不需要写入该表名
2. 不管max_lsn是大于checkpoint的LSN还是小于,都会调用fil_names_write,  写入MLOG_FILE_NAME
/// 追加一条日志MLOG_CHECKPOINT,  记录checkpoint发生的LSN

在函数fil_names_clear中产生的日志聚合在一个mtr中提交。(但这个mtr中实际上包含两个log body, MLOG_CHECKPOINT被当做singl rec)。也就是说,除非clean shutdown之外,每个完整的checkpoint,必然要有对应的mlog_checkpoint日志.

随后将日志刷入磁盘: 这是一个临界点,假如在这里crash了,这意味着checkpoint还没更新下去, MLOG_CHECKPOINT已经写下去了,如果从上次checkpoint的点开始扫描,可能会找到两个MLOG_CHECKPOINT日志

Append on checkpoint:

在看代码时发现一个和旧版本不同的变量log_sys->append_on_checkpoint,指向的是一段redo cache。 在做checkpoint时写MLOG_FILE_NAME之前会先看这个指针是否为空,如果不为空,就将这段cache的日志写到全局Buffer。

在函数ha_innobase::commit_inplace_alter_table中被设置,在ddl的最后一步. 设置和重置append_on_checkpoint是在持有数据词典锁时进行的。如果DDL需要最后做临时表和用户表的交换, 此时会写两条MLOG_FILE_RENAME2日志,第一条是老表rename成一个临时表,第二个是完成DDL的新表rename为老表名

然后将这个日志组拷贝下来,并赋值到log_sys->append_on_checkpoint。根据commit log的描述,主要是解决如下场景:

1. The changes to SYS_TABLES were committed, and MLOG_FILE_RENAME2
records were written in a single mini-transaction commit.
2. A log checkpoint and a server kill was injected.
3. Crash recovery will see no records (other than the MLOG_CHECKPOINT).
4. dict_check_tablespaces_and_store_max_id() will emit a message about
a non-found table #sql-ib22*.
5. A mismatch is triggering the assertion failure.

Crash Recovery

入口函数: recv_recovery_from_checkpoint_start:

在从第一个日志文件ib_logfile0找到checkpoint点后,就可以从该点开始扫描日志:

scan 1: 找到MLOG_CHECKPOINT的位置(STORE_NO)

recv_sys_t::mlog_checkpoint_lsn 记录出现MLOG_CHECKPOINT的日志位置. 找到和checkpoint lsn匹配的MLOG_CHECKPOINT后结束第一次扫描

在扫描的过程中,如果遇到如下几类日志,调用fil_name_parse:


        case MLOG_FILE_NAME:
        case MLOG_FILE_DELETE:
        case MLOG_FILE_CREATE2:
        case MLOG_FILE_RENAME2:

对于涉及到的表名,会调用fil_name_process存储到recv_spaces_t中, 这是个map, 以space_id为Key. 对于MLOG_FILE_NAME or MLOG_FILE_RENAME2, 将对应的space载入内存( fil_ibd_load ). 对于MLOG_FILE_DELETE,如果map中已经存在,将flag设置为deleted,并释放fil_space_t

如果找不到MLOG_CHECKPOINT的话,就认为崩溃恢复失败了(clean shutdown除外)

scan 2:

开始扫描并存储到hash中(STORE_YES),解析到的日志被存储到hash中(不判断tablespace是否存在)。STORE_YES的意思是不去判断是否redo对应的tablespace是否存在或被修改. 因为第二次scan的另外一个目的是搜集所有在checkpoint后被修改过的表空间(MLOG_FILE_NAME)

如果内存不够用于存储redo log时,那就不再将redo存到hash中,但会继续扫描到日志尾部,确保所有被修改的表空间都被检测到了并维护下来。

调用recv_init_crash_recovery_spaces
// 对于被修改过的tablespace,加入到链表fil_system->named_spaces上(fil_names_dirty)
// 如果已经删除的tablespace,就将对应的日志设置为RECV_DISCARDED, 这些日志无需apply
// 根据double write buffer校验及载入(buf_dblwr_process)
// 开启后台进程, 用于flush dirty page (recv_writer_thread)

scan 3:

如果scan 2由于内存不足未完成, 会最后重新扫描,由于scan 2已经确保了 这一轮只将tablespace存在的日志加入到hash中(STORE_IF_EXISTS),如果内存不足了,则直接apply掉,再继续解析.

问题及优化

对于第一次扫描寻找mlog_checkpoint可以做一些优化,在做checkpoint时直接将对应的位点存到checkpoint信息里,这样在崩溃恢复时,就可以直接跳到对应的位置,从而避免扫描. 我们将这个Issue report到官方,已经得到确认,ref: (bug#80788)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值