【MySQL技术内幕】2.5-Master Thread工作方式

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 RDS MySQL,高可用系列 2核4GB
简介: 【MySQL技术内幕】2.5-Master Thread工作方式

InnoDB 存储引擎的主要工作都是在一个单独的后台线程 Master Thread 中完成的,这一节将具体解释该线程的具体实现及该线程可能存在的问题。

1、InnoDB 1.0.x 版本之前的Master Thread

Master Thread 具有最高的线程优先级别。其内部由多个循环( loop )组成:主循环 ( loop )、后台循环( background loop )、刷新循环( flush loop )、暂停循环( suspend loop )。 Master Thread 会根据数据库运行的状态在 loop 、 background loop 、 flush loop 和 suspend loop 中进行切换。

Loop被称为主循环,因为大多数的操作是在这个循环中,其中有两大部分作 ― 每秒钟的操作和每 10 秒的操作。伪代码如下:

vold master thread() {
    loop : for (int i = 0 ; i < 10 ; i ++){
       do thing once per second 
       sleep 1 second if necessary
    } 
    dothings once per ten seconds 
    goto loop;
}

可以看到, for循环通过 thread sleep来实现,这意味着所谓的每秒一次或每 10 秒一次的操作是不精确的。在负载很大的情况下可能会有延迟( delay ) ,只能说大概在这个频率下。当然, InnoDB 源代码中还通过了其他的方法来尽量保证这个频率。每秒一次的操作包括:

  • 日志缓冲刷新到磁盘,即使这个事务还没有提交(总是) ;
  • 合并插人缓冲(可能) ;
  • 至多刷新 100 个 InnoDB 的缓冲池中的脏页到磁盘(可能) ;
  • 如果当前没有用户活动,则切换到 background loop (可能)。

即使某个事务还没有提交, InnoDB 存储引擎仍然每秒会将重做日志缓冲中的内容刷新到重做日志文件。这一点是必须要知道的,因为这可以很好地解释为什么再大的事务提交( commit )的时间也是很短的。合并插人缓冲( Insert Buffer )并不是每秒都会发生的。 InnoDB 存储引擎会判断当前一秒内发生的 IO 次数是否小于 5 次,如果小于 5 次, InnoDB 认为当前的 IO压力很小,可以执行合并插人缓冲的操作。同样,刷新 100 个脏页也不是每秒都会发生的。 InnoDB 存储引擎通过判断当前缓冲池中脏页的比例( buf_get_modified_ratio_pct )是否超过了配置文件中 innodb_max_dirty_pages_pct 这个参数(默认为 90 ,代表 90 % ) ,如果超过了这个阑值, InnoDB 存储引擎认为需要做磁盘同步的操作,将 100 个脏页写人磁盘中。总结上述操作,伪代码可以进一步具体化,如下所示:

void master thread() {
    goto loop;
    loop:
    for (int i = 0; i < 10;i++){
        thread_sleep(1);
        do log buffer flush to disk
        if (last_one_second_ios < 5)
            do merge at most 5 instert buffer;
        if (buf_get_modified_ratio_pct > innodb_max_dirty_pages_pct)
            do buffer pool flush 100 dirty page
        if (no user activity )
            goto backgroud loop
    }
    do things once per ten seconds
    background loop:
        do something 
        goto loop:
}

接着来看每 10 秒的操作,包括如下内容:

  • 刷新 100 个脏页到磁盘(可能的情况下) ;
  • 合并至多 5 个插人缓冲(总是) ;
  • 将日志缓冲刷新到磁盘(总是) ;
  • 删除无用的 Undo页(总是) ;
  • 刷新 100 个或者 10 个脏页到磁盘(总是)。

在以上的过程中, InnoDB 存储引擎会先判断过去10秒之内磁盘的 IO 操作是否小于 200 次,如果是, InnoDB 存储引擎认为当前有足够的磁盘IO操作能力,因此将 100 个脏页刷新到磁盘。接着, InnoDB 存储引擎会合并插人缓冲。不同于每秒一次操作时可能发生的合并插人缓冲操作,这次的合并插入缓冲操作总会在这个阶段进行。之后, InnoDB 存储引擎会再进行一次将日志缓冲刷新到磁盘的操作。这和每秒一次时发生的操作是一样的。

接着 InnoDB 存储引擎会进行一步执行 full purge 操作,即删除无用的 undo 页。对表进行 update 、 delete 这类操作时,原先的行被标记为删除,但是因为一致性读( consistent read )的关系,需要保留这些行版本的信息。但是在 full purge 过程中, InnoDB 存储引擎会判断当前事务系统中已被删除的行是否可以删除,比如有时候可能还有查询操作需要读取之前版本的 undo 信息,如果可以删除, InnoDB 会立即将其删除。

从源代码中可以发现, InnoDB 存储引擎在执行 full purge操作时,每次最多尝试回收20个undo页。

然后InnoDB 存储引擎会判断缓冲池中脏页的比例( buf_get _modified_ratio_pct ) , 如果有超过 70 %的脏页刷新 100个脏页到磁盘,如果脏页的比例小于 70 % ,则只需刷新10%个脏页到磁盘。

现在我们可以完整地把主循环( main loop)的伪代码写出来了,内容如下:

image.png

接着来看 background loop ,若当前没有用户活动(数据库空闲时)或者数据库关闭 ( shutdown ) ,就会切换到这个循环。 background loop 会执行以下操作:

  • 删除无用的 Undo页(总是) ;
  • 合并 20 个插人缓冲(总是) ;
  • 跳回到主循环(总是) ;
  • 不断刷新 100 个页直到符合条件(可能跳转到 flush loop 中完成)。

若 flush foop 中也没有什么事情可以做了, InnoDB 存储引擎会切换到 loop ,将 Master Thread 挂起,等待事件的发生。若用户启用( enable )了 InnoDB 存储引擎,却没有使用任何 InnoDB 存储引擎的表,那么 Master Thread 总是处于挂起的状态。最后,Master Thread 完整的伪代码如下:

image.png

image.png

2、InnoDB 1.2.x版本之前的 Master Thread

在了解了 1.0.x 版本之前的 Master Thread 的具体实现过程后,细心的读者会发现 InnoDB 存储引擎对于IO其实是有限制的,在缓冲池向磁盘刷新时其实都做了一定的硬编码( hard coding )。在磁盘技术飞速发展的今天,当固态磁盘( SSD )出现时,这种规定在很大程度上限制了 InnoDB 存储引擎对磁盘 IO 的性能,尤其是写入性能。

从前面的伪代码来看,无论何时, InnoDB 存储引擎最大只会刷新 100 个脏页到磁盘,合并 20 个插人缓冲。如果是在写入密集的应用程序中,每秒可能会产生大于 100 个的脏页,如果是产生大于 20 个插人缓冲的情况, Master Thread 似乎会“忙不过来” , 或者说它总是做得很慢。即使磁盘能在 l 秒内处理多于 100 个页的写入和 20 个插人缓冲的合并,但是由于 hard coding , Master Thread 也只会选择刷新 100 个脏页和合并 20 个插入缓冲。同时,当发生宕机需要恢复时,由于很多数据还没有刷新回磁盘,会导致恢复的时间可能需要很久,尤其是对于 inscrt buffer 来说。

这个问题最初由 Google 的工程师 Mark Callaghan 提出,之后 InnoDB 官方对其进行了修正并发布了补丁( patch )。 InnoDB 存储引擎的开发团队参考了 Google 的 patch ,提供了类似的方法来修正该问题。因此 InnoDB Plugin (从 InnoDB 1.0.x 版本开始)供了参数 innodb_io_capacity ,用来表示磁盘IO的吞吐量,默认值为 200 。对于刷新到磁盘页的数量,会按照 innodb_io_capaoity 的百分比来进行控制。规则如下:

  • 在合并插人缓冲时,合并插人缓冲的数量为 innodb_io_capacity 值的 5 % ;
  • 在从缓冲区刷新脏页时,刷新脏页的数量为 innodb_io_capacity 。

若用户使用了 SSD 类的磁盘,或者将几块磁盘做了 RAID ,当存储设备拥有更高的 IO 速度时,完全可以将 innodb_io_capacity的值调得再高点,直到符合磁盘IO的吞吐量为止。另一个问题是,参数 innodb_max_dirty_pages_pct 默认值的问题,在 InnoDB 1.0.x 版本之前,该值的默认为 90 ,意味着脏页占缓冲池的 90 %。但是该值“太大”了,因为 InnoDB 存储引擎在侮秒刷新缓冲池和 flush loop 时会判断这个值,如果该值大于innodb_max_dirty_pages_pct ,才刷新 1 00 个脏页,如果有很大的内存,或者数据库服一务器的压力很大,这时刷新脏页的速度反而会降低。同样,在数据库的恢复阶段可能需要更多的时间。

在很多论坛上都有对这个问题的讨论,有人甚至将这个值调到了 20 或 10 ,然后测试发现性能会有所提高,但是将 innodb_max_dirty_pages_pct 调到 20 或 10 会增加磁盘的压力,系统的负担还是会有所增加的。 Google 在这个问题上进行了测试,证明 20 并不是一个最优值。而从 InnoDB 1.0.x 版本开始,innodb_max_dirty_pages_pct默认值变为了 75 ,和 Google测试的 80 比较接近。这样既可以加快刷新脏页的频率,又能保证了磁盘 IO的负载。

InnoDB 1.0.x 版本带来的另一个参数是 innodb_adaptive_fiushing (自适应地刷新) , 该值影响每秒刷新脏页的数觉。原来的刷新规则是:脏页在缓冲池所占的比例小于innodb_max_dirty_pages_pct时,不刷新脏页;大于innodb_max_dirty_pages_pct时,刷新 100 个脏页。随着innodb_adaptive_fiushing参数的引入, InnoDB 存储引擎会通过一个名为 buf_fiush_get_desired_flush_rate 的函数来判断需要刷新脏页最合适的数量。粗略地翻阅源代码后发现 buf_fiush_get_desired_flush_rate 通过判断产生重做日志( redo log )的速度来决定最合适的刷新脏页数量。因此,当脏页的比例小于 innodb_max_dirty_pages_pct 时,也会刷新一定量的脏页。

还有一个改变是:之前每次进行 fun purge 操作时,最多回收 20个Undo 页,从 InnoDB 1.0.x版本开始引人了参数 innodb_purge_batch_size ,该参数可以控制每次 full purge 回收的 undo页的数量。该参数的默认值为 20(MySQL5.7为300) ,并可以动态地对其进行修改,具体如下:

mysql> show variables like 'innodb_purge_batch_size';
+-------------------------+-------+
| Variable_name           | Value |
+-------------------------+-------+
| innodb_purge_batch_size | 300   |
+-------------------------+-------+
1 row in set (0.01 sec)
 
mysql> set global innodb_purge_batch_size=50;
Query OK, 0 rows affected (0.01 sec)

通过上述的讨论和解释我们知道,从 InnoDB 1.0.X版本开始, Master Thread伪代码必将有所改变,最终变成:

image.png

很多测试都显示, InnoDB 1.0.x 版本在性能方面取得了极大的提高,和前面提到的 Master Thread 的改动是密不可分的,因为 InnoDB 存储引擎的核心操作大部分都集中在 Master Thread 后台线程中。从 InnoDB 1.0.x 开始,命令 SHOW ENGINE INNODB STATUS 可以查看当前 Master Thread 的状态信息,如下所示:

image.png

这里可以看到主循环进行了 45 次,每秒挂起( sleep )的操作进行了 45 次(说明负载不是很大) , 10秒一次的活动进行了4 次,符合 1 : 10 。 background loop 进行了 6 次, flushfo 叩也进行了 6 次。因为当前这台服务器的压力很小,所以能在理论值上运行。如果是在一台压力很大的 MySQL 数据库服务器上,看到的可能会是下面的情景:

image.png

可以看到当前主循环运行了 2188 次,但是循环中的每秒挂起( sleep )的操作只运行了 1537 次。这是因为 lnnoDB 对其内部进行了一些优化,当压力大时并不总是等待1秒。因此,并不能认为1_second 和 sleeps 的值总是相等的。在某些情况下,可以通过两者之间差值的比较来反映当前数据库的负载压力。

3、InnoDB 1. 2.x 版本的 Master Thread

在 InnoDB 1. 2.x 版本中再次对 Maste Thread 进行了优化,由此也可以看出 Master Thread 对性能所起到的关键作用。在 InnoDB 1. 2.x 版本中,Master Thread 的伪代码如下:

image.png

其中 sry_master_do_idle_tasks()就是之前版本中每 10 秒的操作, sry_master_do_active_tasks()处理的是之前每秒中的操作。同时对于刷新脏页的操作,从 Master Thread 线程分离到一个单独的 Page Cleaner Thread ,从而减轻了 Master Thread 的工作,同时进一步提高了系统的并发性。


相关实践学习
如何快速连接云数据库RDS MySQL
本场景介绍如何通过阿里云数据管理服务DMS快速连接云数据库RDS MySQL,然后进行数据表的CRUD操作。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
7月前
|
监控 关系型数据库 MySQL
10亿数据如何最快速插入MySQL:技术干货分享
【8月更文挑战第2天】在大数据时代,处理并快速插入数十亿条数据到MySQL数据库是许多企业面临的关键挑战。本文将深入分享一系列高效的技术策略和实战经验,帮助读者优化这一过程,确保数据能够快速、准确地进入数据库系统。
320 1
|
3月前
|
JavaScript 安全 Java
java版药品不良反应智能监测系统源码,采用SpringBoot、Vue、MySQL技术开发
基于B/S架构,采用Java、SpringBoot、Vue、MySQL等技术自主研发的ADR智能监测系统,适用于三甲医院,支持二次开发。该系统能自动监测全院患者药物不良反应,通过移动端和PC端实时反馈,提升用药安全。系统涵盖规则管理、监测报告、系统管理三大模块,确保精准、高效地处理ADR事件。
210 1
|
4月前
|
监控 前端开发 Java
【技术开发】接口管理平台要用什么技术栈?推荐:Java+Vue3+Docker+MySQL
该文档介绍了基于Java后端和Vue3前端构建的管理系统的技术栈及功能模块,涵盖管理后台的访问、登录、首页概览、API接口管理、接口权限设置、接口监控、计费管理、账号管理、应用管理、数据库配置、站点配置及管理员个人设置等内容,并提供了访问地址及操作指南。
|
4月前
|
监控 关系型数据库 MySQL
MySQL自增ID耗尽应对策略:技术解决方案全解析
在数据库管理中,MySQL的自增ID(AUTO_INCREMENT)属性为表中的每一行提供了一个唯一的标识符。然而,当自增ID达到其最大值时,如何处理这一情况成为了数据库管理员和开发者必须面对的问题。本文将探讨MySQL自增ID耗尽的原因、影响以及有效的应对策略。
310 3
|
5月前
|
XML 关系型数据库 MySQL
MySQL 导出某些数据的技术详解
MySQL 导出某些数据的技术详解
273 2
|
6月前
|
存储 关系型数据库 MySQL
技术解析:MySQL中取最新一条重复数据的方法
以上提供的两种方法都可以有效地从MySQL数据库中提取每个类别最新的重复数据。选择哪种方法取决于具体的使用场景和MySQL版本。子查询加分组的方法兼容性更好,适用于所有版本的MySQL;而窗口函数方法代码更简洁,执行效率可能更高,但需要MySQL 8.0及以上版本。在实际应用中,应根据数据量大小、查询性能需求以及MySQL版本等因素综合考虑,选择最合适的实现方案。
578 6
|
5月前
|
关系型数据库 MySQL 数据库
MySQL技术深度解析:每次最大插入条数探秘
MySQL技术深度解析:每次最大插入条数探秘
128 0
|
5月前
|
关系型数据库 MySQL 数据库管理
MySQL技术指南:如何更改数据字段的前几位数字
MySQL技术指南:如何更改数据字段的前几位数字
111 0
|
5月前
|
消息中间件 监控 关系型数据库
MySQL数据实时同步到Elasticsearch:技术深度解析与实践分享
在当今的数据驱动时代,实时数据同步成为许多应用系统的核心需求之一。MySQL作为关系型数据库的代表,以其强大的事务处理能力和数据完整性保障,广泛应用于各种业务场景中。然而,随着数据量的增长和查询复杂度的提升,单一依赖MySQL进行高效的数据检索和分析变得日益困难。这时,Elasticsearch(简称ES)以其卓越的搜索性能、灵活的数据模式以及强大的可扩展性,成为处理复杂查询需求的理想选择。本文将深入探讨MySQL数据实时同步到Elasticsearch的技术实现与最佳实践。
365 0
|
7月前
|
SQL 存储 关系型数据库
mysql加索引真的会锁表吗?揭秘背后的技术细节与规避策略
【8月更文挑战第16天】在数据库管理中,添加索引能大幅提升查询效率。MySQL执行此操作时的锁定行为常引起关注。文章详细解析MySQL中索引添加时的锁定机制及其原理。不同存储引擎及SQL语句影响锁定策略:MyISAM需全表锁定;InnoDB提供更灵活选项,如使用`ALTER TABLE... LOCK=NONE`可在加索引时允许读写访问,尽管可能延长索引构建时间。自MySQL 5.6起,在线DDL技术可进一步减少锁定时间,通过`ALGORITHM=INPLACE`和`LOCK=NONE`实现近乎无锁的表结构变更。合理配置这些选项有助于最小化对业务的影响并保持数据库高效运行。
816 4