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

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
日志服务 SLS,月写入数据量 50GB 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)

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
19天前
|
SQL 监控 关系型数据库
MySQL优化: CPU高 处理脚本 pt-kill脚本
MySQL优化: CPU高 处理脚本 pt-kill脚本
|
18天前
|
缓存 关系型数据库 MySQL
在Linux中,如何优化MySQL性能,包括索引优化和查询分析?
在Linux中,如何优化MySQL性能,包括索引优化和查询分析?
|
19天前
|
关系型数据库 MySQL
MySQl优化:使用 jemalloc 分配内存
MySQl优化:使用 jemalloc 分配内存
|
25天前
|
算法 关系型数据库 MySQL
一天五道Java面试题----第七天(mysql索引结构,各自的优劣--------->事务的基本特性和隔离级别)
这篇文章是关于MySQL的面试题总结,包括索引结构的优劣、索引设计原则、MySQL锁的类型、执行计划的解读以及事务的基本特性和隔离级别。
|
28天前
|
存储 关系型数据库 MySQL
MySQL 上亿大表,如何深度优化?
【8月更文挑战第11天】随着大数据时代的到来,MySQL 作为广泛使用的关系型数据库管理系统,经常需要处理上亿级别的数据。当数据量如此庞大时,如何确保数据库的查询效率、稳定性和可扩展性,成为了一个亟待解决的问题。本文将围绕 MySQL 上亿大表的深度优化,分享一系列实用的技术干货,帮助你在工作和学习中应对挑战。
40 1
|
16天前
|
关系型数据库 MySQL 数据库连接
绝对干货!从MySQL5.7平滑升级到MySQL8.0的最佳实践分享
绝对干货!从MySQL5.7平滑升级到MySQL8.0的最佳实践分享
28 0
|
23天前
|
存储 SQL 关系型数据库
探索MySQL的执行奥秘:从查询执行到数据存储与优化的深入解析
探索MySQL的执行奥秘:从查询执行到数据存储与优化的深入解析
|
17天前
|
SQL 关系型数据库 MySQL
【揭秘】MySQL binlog日志与GTID:如何让数据库备份恢复变得轻松简单?
【8月更文挑战第22天】MySQL的binlog日志记录数据变更,用于恢复、复制和点恢复;GTID为每笔事务分配唯一ID,简化复制和恢复流程。开启binlog和GTID后,可通过`mysqldump`进行逻辑备份,包含binlog位置信息,或用`xtrabackup`做物理备份。恢复时,使用`mysql`命令执行备份文件,或通过`innobackupex`恢复物理备份。GTID模式下的主从复制配置更简便。
70 2
|
12天前
|
弹性计算 关系型数据库 数据库
手把手带你从自建 MySQL 迁移到云数据库,一步就能脱胎换骨
阿里云瑶池数据库来开课啦!自建数据库迁移至云数据库 RDS原来只要一步操作就能搞定!点击阅读原文完成实验就可获得一本日历哦~
|
16天前
|
关系型数据库 MySQL 数据库
RDS MySQL灾备服务协同解决方案构建问题之数据库备份数据的云上云下迁移如何解决
RDS MySQL灾备服务协同解决方案构建问题之数据库备份数据的云上云下迁移如何解决

热门文章

最新文章

下一篇
DDNS