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

本文涉及的产品
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
云数据库 RDS MySQL Serverless,价值2615元额度,1个月
简介: 在MySQL5.7之前的版本中, InnoDB每次做crash recovery之前都需要扫描数据目录,打开每个文件并创建内存对象。当目录下文件个数特别多时,会严重影响到崩溃恢复的速度。 为了解决这个问题,MySQL5.7通过结合checkpoint + 标注被修改的文件的方式,从一个check

在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)

相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
5天前
|
关系型数据库 MySQL 测试技术
MySQL的`IN`的优化经验
限制IN列表的长度:IN子句中的元素数量较多时,会显著降低查询性能。尽量减少IN中的项数量。
|
9天前
|
关系型数据库 MySQL Unix
linux优化空间&完全卸载mysql——centos7.9
linux优化空间&完全卸载mysql——centos7.9
41 7
|
10天前
|
SQL 存储 关系型数据库
不允许你不知道的 MySQL 优化实战(三)
不允许你不知道的 MySQL 优化实战(三)
14 1
|
10天前
|
SQL 关系型数据库 MySQL
不允许你不知道的 MySQL 优化实战(二)
不允许你不知道的 MySQL 优化实战(二)
16 2
|
10天前
|
SQL 关系型数据库 MySQL
MySQL8.0索引新特性
MySQL8.0索引新特性
13 0
|
3天前
|
SQL 安全 关系型数据库
【Mysql-12】一文解读【事务】-【基本操作/四大特性/并发事务问题/事务隔离级别】
【Mysql-12】一文解读【事务】-【基本操作/四大特性/并发事务问题/事务隔离级别】
|
3天前
|
存储 关系型数据库 MySQL
【MySQL系列笔记】InnoDB引擎-数据存储结构
InnoDB 存储引擎是MySQL的默认存储引擎,是事务安全的MySQL存储引擎。该存储引擎是第一个完整ACID事务的MySQL存储引擎,其特点是行锁设计、支持MVCC、支持外键、提供一致性非锁定读,同时被设计用来最有效地利用以及使用内存和 CPU。因此很有必要学习下InnoDB存储引擎,它的很多架构设计思路都可以应用到我们的应用系统设计中。
24 4
|
5天前
|
弹性计算 关系型数据库 MySQL
在线安装MySQL5.7和MySQL8.0
【4月更文挑战第30天】
15 0
|
8天前
|
SQL 关系型数据库 MySQL
【MySQL】SQL优化
【MySQL】SQL优化
|
10天前
|
SQL 关系型数据库 MySQL
【mysql】mysq优化全方面分析
【mysql】mysq优化全方面分析
13 0