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的并发线程,扫描进行二次分区的情况。

相关代码如下:

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/

(0)

相关推荐

  • mysql 设置innodb_lock_wait_timeout 时间方法

    设置mysql 事务锁超时时间 innodb_lock_wait_timeout Mysql数据库采用InnoDB模式,默认参数:innodb_lock_wait_timeout设置锁等待的时间是50 ...

  • 高性能MySQL【笔记】超详细

    一.MySQL架构与历史 A.并发控制1.共享锁(shared lock,读锁):共享的,相互不阻塞的 2.排他锁(exclusive lock,写锁):排他的,一个写锁会阻塞其他的写锁和读锁 B.事 ...

  • 关于MySQL内存泄露如何排查的一些思路

    MySQL使用内存上升90%!在运维过程中50%的几率,会碰到这样的问题.算是比较普遍的现象. MySQL内存使用率过高,有诸多原因.普遍原因是使用不当,还有MySQL本身缺陷导致的.到底是哪方面的问 ...

  • MySQL8.0新特性

    MySQL从5.7一跃直接到8.0,这其中的缘由,咱就不关心那么多了,有兴趣的朋友自行百度,本次的版本更新,在功能上主要有以下6点: 账户与安全 优化器索引 通用表表达式 窗口函数 InnoDB 增强 ...

  • 正则表达式在MySQL5.7和MySQL8.0中的用法以及区别(一)

    各位客官姥爷好,欢迎回来.我们上节给出了MySQL数据库的安装教程,我们上节给的是5.7版本的安装教程,鉴于有部分企业已经使用8.0版本的MySQL,那么下面我将对比着学习正则表达式在这两个版本中的差 ...

  • 正则表达式在MySQL5.7和MySQL8.0中的用法以及区别(二)

    各位客官姥爷好,欢迎回来.我们上节给出了正则表达式在MySQL5.7与MySQL8.0中用法的区别,下面我们将继续正则表达式在Mysql数据库中使用系列内容. 元字符 1. \s:任意空白符      ...

  • 正则表达式在MySQL5.7和MySQL8.0中的用法以及区别(三)

    各位客官姥爷好,欢迎回来.我们上节给出了正则表达式在MySQL5.7与MySQL8.0中用法的区别(二),下面我们将继续正则表达式在Mysql数据库中使用系列内容. 准备工作 在之前的测试过程中,我们 ...

  • 正则表达式在MySQL5.7和MySQL8.0中的用法以及区别(应用篇)

    各位客官姥爷好,欢迎回来.相信大家通过前面的内容对正则在mysql中的用法有一定的认识,那本节我们通过几个应用示例来回忆下这些用法. 这里依旧使用上节创建的测试表user,上节内容传送门:MySQL正 ...

  • win10 安装 MySQL8.0版本

    最近在搭建基于Nest的Web API的项目,需要用到数据库,于是在我的本机win10上装MySQL,本以为是件很简单的事情,却是遇到各种问题,所以记录下,方便后期问题排查. 1 下载 首先下载安装包 ...

  • powerdesigner error [MySQL][ODBC 5.3 Driver][mysql-8.0.22]Source character set not supported by client]

    此问题主要是利用 power designer 采用 odbc连接mysql都可以测试成功,但是无论是调用逆向工程,还是生成数据库 都会报错. 参考这篇博文: https://www.cnblogs. ...

  • MySQL8.0的caching

    解决方法 最后搜索了下众多网友的解决方案,都是将验证方式修改为上一版的,使用mysql进入控制台,然后输入ALTER USER root@localhost IDENTIFIED WITH mysql ...

  • windows server2019下安装mysql8.0.11

    一些说明 上篇写了安装mysql5.版本,8.和5.的加密方式不同 其他安装上基本相同 https://downloads.mysql.com/archives/community/ 配置初始化的my ...