MySQL8.0 InnoDB并行执行

概述

MySQL经过多年的发展已然成为最流行的数据库,广泛用于互联网行业,并逐步向各个传统行业渗透。之所以流行,一方面是其优秀的高并发事务处理的能力,另一方面也得益于MySQL丰富的生态。MySQL在处理OLTP场景下的短查询效果很好,但对于复杂大查询则能力有限。最直接一点就是,对于一个SQL语句,MySQL最多只能使用一个CPU核来处理,在这种场景下无法发挥主机CPU多核的能力。MySQL没有停滞不前,一直在发展,新推出的8.0.14版本第一次引入了并行查询特性,使得check table和select count(*)类型的语句性能成倍提升。虽然目前使用场景还比较有限,但后续的发展值得期待。

使用方式

通过配置参数innodb_parallel_read_threads来设置并发线程数,就能开始并行扫描功能,默认这个值为4。我这里做一个简单的实验,通过sysbench导入2亿条数据,分别配置innodb_parallel_read_threads为1,2,4,8,16,32,64,测试并行执行的效果。测试语句为select count(*) from sbtest1;

横轴是配置并发线程数,纵轴是语句执行时间。从测试结果来看,整个并行表现还是不错的,扫描2亿条记录,从单线程的18s,下降到32线程的1s。后面并发开再多,由于数据量有限,多线程的管理消耗超过了并发带来的性能提升,不能再继续缩短SQL执行时间。

MySQL并行执行

实际上目前MySQL的并行执行还处于非常初级阶段,如下图所示,左边是之前MySQL串行处理单个SQL形态;中间的是目前MySQL版本提供的并行能力,InnoDB引擎并行扫描的形态;最右边的是未来MySQL要发展的形态,优化器根据系统负载和SQL生成并行计划,并将分区计划下发给执行器并行执行。并行执行不仅仅是并行扫描,还包括并行聚集,并行连接,并行分组,以及并行排序等。目前版本MySQL的上层的优化器以及执行器并没有配套的修改。因此,下文的讨论主要集中在InnoDB引擎如何实现并行扫描,主要包括分区,并行扫描,预读以及与执行器交互的适配器类。

分区

并行扫描的一个核心步骤就是分区,将扫描的数据划分成多份,让多个线程并行扫描。InnoDB引擎是索引组织表,数据以B+tree的形式存储在磁盘上,节点的单位是页面(block/page),同时缓冲池中会对热点页面进行缓存,并通过LRU算法进行淘汰。分区的逻辑就是,从根节点页面出发,逐层往下扫描,当判断某一层的分支数超过了配置的线程数,则停止拆分。在实现时,实际上总共会进行两次分区,第一次是按根节点页的分支数划分分区,每个分支的最左叶子节点的记录为左下界,并将这个记录记为相邻上一个分支的右上界。通过这种方式,将B+tree划分成若干子树,每个子树就是一个扫描分区。经过第一次分区后,可能出现分区数不能充分利用多核问题,比如配置了并行扫描线程为3,第一次分区后,产生了4个分区,那么前3个分区并行做完后,第4个分区至多只有一个线程扫描,最终效果就是不能充分利用多核资源。

二次分区

为了解决这个问题,8.0.17版本引入了二次分区,对于第4个分区,继续下探拆分,这样多个子分区又能并发扫描,InnoDB引擎并发扫描的最小粒度是页面级别。具体判断二次分区的逻辑是,一次分区后,若分区数大于线程数,则编号大于线程数的分区,需要继续进行二次分区;若分区数小于线程数且B+tree层次很深,则所有的分区都需要进行二次分区。相关代码如下:

split_point = 0;
if (ranges.size() > max_threads()) {
   //最后一批分区进行二次分区
   split_point = (ranges.size() / max_threads()) * max_threads();
 } else if (m_depth < SPLIT_THRESHOLD) {
   /* If the tree is not very deep then don't split. For smaller tables
   it is more expensive to split because we end up traversing more blocks*/
   split_point = max_threads();
 } else {
   //如果B+tree的层次很深(层数大于或等于3,数据量很大),则所有分区都需要进行二次分区
 }

无论是一次分区,还是二次分区,分区边界的逻辑都一样,以每个分区的最左叶子节点的记录为左下界,并且将这个记录记为相邻上一个分支的右上界。这样确保分区足够多,粒度足够细,充分并行。下图展示了配置为3的并发线程,扫描进行二次分区的情况。

MySQL8.0 InnoDB并行执行

相关代码如下:

create_ranges(size_t depth, size_t level)
一次分区:
parallel_check_table
 add_scan
   partition(scan_range, level=0)  /* start at root-page */
     create_ranges(scan_range, depth=0, level=0)
   create_contexts(range, index >= split_point)
二次分区:
split()
 partition(scan_range, level=1)
   create_ranges(depth=0,level)

并行扫描

在一次分区后,将每个分区扫描任务放入到一个lock-free队列中,并行的worker线程从队列中获取任务,执行扫描任务,如果获取的任务带有split属性,这个时候worker会将任务进行二次拆分,并投入到队列中。这个过程主要包括两个核心接口,一个是工作线程接口,另外一个是遍历记录接口,前者从队列中获取任务并执行,并维护统计计数;后者根据可见性获取合适的记录,并通过上层注入的回调函数处理,比如计数等。

Parallel_reader::worker(size_t thread_id)

{

1.从ctx-queue提取ctx任务

2.根据ctx的split属性,确定是否需要进一步拆分分区(split())

3.遍历分区所有记录(traverse())

4.一个分区任务结束后,维护m_n_completed计数

5.如果m_n_compeleted计数达到ctx数目,唤醒所有worker线程结束

6.根据traverse接口,返回err信息。

}

Parallel_reader::Ctx::traverse()

{

1.根据range设置pcursor

2.找到btree,将游标定位到range的起始位置

3.判断可见性(check_visibility)

4.如果可见,根据回调函数计算(比如统计)

5.向后遍历,若达到了页面的最后一条记录,启动预读机制(submit_read_ahead)

6.超出范围后结束

}

同时在8.0.17版本还引入了预读机制,避免因为IO瓶颈导致并行效果不佳的问题。目前预读的线程数不能配置,在代码中硬编码为2个线程。每次预读的单位是一个簇(InnoDB文件通过段,簇,页三级结构管理,一个簇是一组连续的页),根据页面配置的大小,可能为1M或者2M。对于常见的16k页面配置,每次预读1M,也就是64个页面。worker线程在进行扫描时,会先判断相邻的下一个页面是否为簇的第一个页面,如果是,则发起预读任务。预读任务同样通过lock-free 队列缓存,worker线程是生产者,read-ahead-worker是消费者。由于所有分区页面没有重叠,因此预读任务也不会重复。

执行器交互(适配器)

实际上,MySQL已经封装了一个适配器类Parallel_reader_adapter来供上层使用,为后续的更丰富的并行执行做准备。首先这个类需要解决记录格式的问题,将引擎层扫描的记录转换成MySQL格式,这样做到上下层解耦,执行器不用感知引擎层格式,统一按MySQL格式处理。整个过程是一个流水线,通过一个buffer批量存储MySQL记录,worker线程不停的将记录从引擎层上读上来,同时有记录不停的被上层处理,通过buffer可以平衡读取和处理速度的差异,确保整个过程流动起来。缓存大小默认是2M,根据表的记录行长来确定buffer可以缓存多少个MySQL记录。核心流程主要在process_rows接口中,流程如下

process_rows

{

1.将引擎记录转换成MySQL记录

2.获取本线程的buffer信息(转换了多少mysql记录,发送了多少给上层)

3.将MySQL记录填充进buffer,自增统计m_n_read

4.调用回调函数处理(比如统计,聚合,排序等),自增统计m_n_send

}

对于调用者来说,需要设置表的元信息,以及注入处理记录回调函数,比如处理聚集,排序,分组的工作。回调函数通过设置m_init_fn,m_load_fn和m_end_fn来控制。

总结

MySQL8.0引入了并行查询虽然还比较初级,但已经让我们看到了MySQL并行查询的潜力,从实验中我们也看到了开启并行执行后,SQL语句执行充分发挥了多核能力,响应时间急剧下降。相信在不久的将来,8.0的会支持更多并行算子,包括并行聚集,并行连接,并行分组以及并行排序等。

参考文档

https://dev.mysql.com/worklog/task/?id=11720

https://dev.mysql.com/worklog/task/?id=12978

https://yq.aliyun.com/articles/691516?utm_content=g_1000045831

http://mysql.taobao.org/monthly/2019/10/02/

https://www.percona.com/blog/2019/01/17/using-parallel-query-with-amazon-aurora-for-mysql/

Original: https://www.cnblogs.com/cchust/p/12347166.html
Author: 天士梦
Title: MySQL8.0 InnoDB并行执行

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/591710/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 如何使用Intellij IDEA工具导入SVN项目

    步骤一:选择VCS 打开Intellij IDEA开发工具,在导航栏中选择 VCS栏位,如图。 步骤二:创建SVN地址 执行步骤二,可以看见打开了一个SVN Repositorie…

    数据库 2023年6月6日
    083
  • Java 中的线程池

    为什么要用线程池 在 HotSpot VM 的线程模型中,Java 线程被一对一映射为内核线程。 Java 在使用线程执行程序时,需要调用操作系统内核的 API,创建一个内核线程,…

    数据库 2023年6月11日
    068
  • mysql数据库 Window下安装

    关系数据库,是建立在关系数据库模型基础上的数据库,借助于集合代数等概念和方法来处理数据 库中的数据,同时也是一个被组织成一组拥有正式描述性的表格,该形式的表格作用的实质是装载着数 …

    数据库 2023年6月9日
    094
  • Javaweb07-三层架构(BaseDao)

    1、BaseDao 持久层业务接口实现类的公共父类,定义了jdbc操作数据库的所有公共方法,方便子类继承; import java.io.InputStream; import j…

    数据库 2023年6月16日
    0102
  • 当mysql表从压缩表变成普通表会发生什么

    本文章做了把mysql表从压缩表过渡到普通表的实验过程,看看压缩表变成普通表会发生什么?本文针对mysql5.7和mysql8分别进行了实验。 1、什么是表压缩 在介绍压缩表变成普…

    数据库 2023年6月16日
    074
  • centos安装chrome和解决chrom点击打不开页面的问题

    1、去官网下载chrom的rpm包 2、进入到下载包的目录,执行:yum -y localinstall google-chrome-stable_current_x86_64.r…

    数据库 2023年6月11日
    089
  • Zabbix-(1)安装

    环境: VMware Workstation Pro 16.0 &#x7248;&#x672C; &#x7CFB;&#x7EDF; Centos7 …

    数据库 2023年6月14日
    070
  • SQL注入学习

    SQL注入学习——资源、笔记整理 OWASP-top10(2021) SQL注入产生原因:注入产生的原因是接受相关参数未经处理直接带入数据库查询操作;注入攻击属于服务端攻击,他与操…

    数据库 2023年6月9日
    074
  • MySQL8.0解决“MySQL 服务无法启动。 服务没有报告任何错误。”

    TL;NRs 初始化服务时最好使用 mysqld –initialized –console命令; MySQL8.0的配置变量与MySQL5.7不同, [mysqld]下面设置…

    数据库 2023年5月24日
    0136
  • Centos7环境使用Mysql离线安装包安装Mysql5.7

    服务器环境:centos7 x64 需要安装:mysql5.7+ 1)检查mysql组合用户是否存在 2)若不存在,则创建mysql组和用户 对于版本选择,您可以选择以下两种方式:…

    数据库 2023年5月24日
    078
  • 优秀体现在细微处

    上面是团队内小王同学整理的需求评审计划,我们看最后一列的细节之处,即,加上了与这个需求相关的资料。 希望我们都能像上面的小王同学一样。 我们的信息,从不同角度来看,总会分散在不同的…

    数据库 2023年6月9日
    091
  • Mysql之Binlog

    1、简述 binlog 二进制日志文件,这个文件记录了MySQL所有的DML操作。通过binlog日志我们可以做数据恢复,增量备份,主主复制和主从复制等等。 2、Docker中无法…

    数据库 2023年6月16日
    093
  • zabbix模板,角色,用户,权限管理

    用户管理 用户组 用户角色 用户 模板管理 模板组 模板 posted @2022-09-07 22:22 溜溜威 阅读(16 ) 评论() 编辑 Original: https:…

    数据库 2023年6月14日
    0103
  • 【JDBC】编程(2)— 写一个JDBC工具类;用 JDBC工具类 实现模糊查询

    JDBC工具类 功能: 简化JDBC部分代码书写 import java.sql.*; public class DBUtil { /** * &#x5DE5;&#…

    数据库 2023年5月24日
    0102
  • EXCLE中SUMIF和SUMIFS的用法

    一、函数的含义 1、SUMIF函数在EXCLE中是用来对指定条件对若干单元格求和; 2、SUMIFIS函数在EXCLE中是用来对一个区域内满足多重条件的单元格求和(两个条件以上);…

    数据库 2023年6月11日
    086
  • IntelliJ IDEA community 安装教程

    jetbrains官网下载 IntelliJ IDEA安装包 此处选择社区版的zip文件 下载完成后解压安装包,此处解压目录为 E:\IntelliJ IDEA\ 开始安装首先添加…

    数据库 2023年6月11日
    0103
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球