数据库学术界的研究热点也一直都是如何提高并行能力(如mass-tree),在工业界,以POLARDB,HANA为代表的新型数据库,都把并行处理的能力做为自己的核心竞争力,然而MySQL官方演进的节奏一直偏慢,迟迟没有推出自己的并行处理方案,直到8.0.14,INNODB团队首次推出了parallel read,先看release notes:

从官方的介绍上看,第一个版本的pread仅仅支持check table以及select count(),从代码提交记录看,编码最早可以追溯到2018年1月,经历了一年多的时间的研发, 可以说这一步迈得非常谨慎,不过我们相信这套并行执行的框架随着新版本的不断推出,通用性以及适用场景会越来越强。

在这篇文章里,我们一起来探索一下原厂是如何实现并行查询的:

Parallel Read的使用方法延续了MySQL简单易用的传统,仅增加了一个innodb会话级别的参数,取值从1~256。如果不指定,默认线程数是4;

这里需要注意,最大256线程同样是实例级别限制,遵循先得先到的原则,一旦threads消耗完,后续的并行查询只能回退到传统的一行行scan。

打开parallel read方法也很简单,

set local innodb_parallel_read_threads=64;

count查询就会走到并行的逻辑里,略微遗憾的是,这个功能还在初级阶段,最新的release版本仅仅支持check table和count查询。

接下来我们对parallel read做一个性能评测,测试服务器的cpu是 Intel(R) Xeon(R) CPU E5-2682 @ 2.50GHz,总共32个逻辑核,因此我们期望在32线程时获得最低的延时,并且有接近线性的加速比。

从这个结果看,parellel read的表现是符合我们的预期的,在低并发的时候可以获得接近线程的加速比,整体可以把延时从22秒降低到1.2秒。

高并发时加速比下降明显,这也是因为多线程调度的overhead太大导致的。

我们也观察到超线程对并行执行的效果不理想,并发数超过了cpu核数之后,出现了明显的性能衰退,可见当前的实现并不能很好的利用cpu的流水线,在榨干cpu性能方面还有很大的潜力。

1. 主要数据结构
  1. Parallel_reader

    并行查义的执行主体对象,主要提供三个接口:

    • add_scan()

      把scan的目标index注册到reader里,虽然当前仅支持clustered index,但从接口的设计看,未来会支持多个index,甚至多个table的parallel scan。

      这里还会对B+tree进行预分片,为什么是预分片,主要原因还是add_scan是单线程执行,计算需要尽可能的轻量,后面的执行线程会做更细粒度的分片,这样的设计带来的好处后面做进一步的解释。

    • run()

  2. Parallel_reader::Ctx

    执行的上下文,对应B+ tree的一个分片。

2. B+tree 分片策略

parallel read自从8.0.14 release之后,有过一个大的改动,就是修复分片算法上的一个设计缺陷。

在最初的设计里,把 B+tree 切成N个subtrees的策略很简单,假如并发度是N,从最一层开始逐层扫描每层的节点数,找到节点数大于N的一层。

假如我们有4个线程,找到了5个sub-trees,这时就会出现数据倾斜,必然有个线程需要处理两个sub-trees,而此时其他线程都是IDLE的状态。

这个问题在数据量非常大的时候会比较明显,因此在8.0.18,对这个算法进行了重新设计,具体的做法如下:

前面提到了add_scan()时做第一次分片,这时粒度是比较粗的,worker线程需要进行二次分片,通常此时,整体B+tree会切成粒度很细的sub-trees,数量远远超过work threads,从而比较优雅的解决数据倾斜的问题;

3. 数据预读

pre-fetching也是后面引入的优化,假如数据都在不BufferPool中,scan table的bottlenect就不是cpu,而是IO,增加cpu效果自然不理想。对于逻辑上的顺序读,一个常见的优化是,额外采用一组线程,提前把数据从磁盘读到BufferPool中,尽可能减少scan时的IO。

4. Handler API