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)

大家都在看

  • 优化 JS 程序的一个小方法

    就像在学习之前先要识字,我想在介绍优化 JavaScript 代码之前,先介绍一下自己对编程语言的理解。故事要从一只叫做 Theseus 的机械鼠和其发明人克劳德-香农(Claud…

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

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

    数据库 2023年6月11日
    082
  • 设计模式之(4)——单例模式

    定义:单例模式属于创建型模式,该类负责创建自己的对象实例,并且确保只有单个对象被创建,同时该类提供了一种全局访问其唯一实例对象的方式;这个定义中有三个要点:1、单例类只能有一个实例…

    数据库 2023年6月14日
    075
  • 常见的位操作及其应用

    概述 与、或、异或、取反或者移位运算这几种基本的位操作想必诸位读者并不陌生,如果我们能在某些合适场景下使用位运算,有些时候可以大大提高算法的效率。但由于本身位运算太过灵活,甚至某些…

    数据库 2023年6月11日
    070
  • TypeScript语言基础

    一、什么是TypeScript 编程语言包括动态类型语言和静态类型语言。动态类型语言是指在程序运行阶段才检查变量数据类型的语言,在定义变量时不需要指定变量的数据类型,通常在编译时不…

    数据库 2023年6月14日
    084
  • Mysql的知识梳理

    数据准备: –建表 create table customer_jia(CID int(4), Cname varchar(20), Csex varchar(2), …

    数据库 2023年6月16日
    076
  • Nginx进阶篇—web模块及proxy代理

    server { #&#x5728;server{&#x4E0B;&#x9762;&#x8F93;&#x5165; sub_filter n…

    数据库 2023年6月14日
    096
  • 记一次MySql唯一索引在left join连表查询没走索引的问题

    在新建一张账单结算信息表bill_settlement_info的时候,建立的唯一索引uk_bill_no(bill_no,tenant_id)。由于列表查询用到该表的字段。所以在…

    数据库 2023年6月16日
    061
  • StoneDB for MySQL 5.7 版本发布

    StoneDB_5.7_v1.0.0 的发行日志 支持MySQL 5.7 功能添加或改变 编译相关改动 配置相关改动 文档变更 BUG修复 欢迎大家关注我们的Github:http…

    数据库 2023年6月11日
    067
  • Mysql的读写分离中间件该怎么写?听我来说。

    网上有很多读写分离的中间件,像proxy,mycat等等,由于本人比较懒,懒得去读各种开源的东西,还是想造轮子来得快。 1、了解mysql通信协议,其中有分4.1之前和4.1版本的…

    数据库 2023年6月14日
    090
  • SkyWalking Agent端日志插件的编写历程与使用说明

    前一段时间顺利完成了SkyWalking Agent端logger-plugin插件的开发,在此做个总结。一方面给插件的使用方法写一中文说明,另一方面分享一下该插件开发过程中的一些…

    数据库 2023年6月11日
    061
  • MySQL实战45讲 9

    09 | 普通索引和唯一索引,应该怎么选择? 每个人都有一个唯一的身份证号,而且业务代码已经保证了不会写入两个重复的身份证号。如果市民系统需要按照身份证号查姓名,就会执行类似这样的…

    数据库 2023年6月16日
    0101
  • mysql安装及主从复制配置

    一、安装 mysql8.0 下载mysql 安装包http://mirrors.sohu.com/mysql/MySQL-8.0/ wget http://mirrors.sohu…

    数据库 2023年5月24日
    056
  • java读取文本文件的方法

    文本文件是我们日常开发中,常用的简单存储载体,那么如何读取文本文件呢? 下文笔者将通过示例的方式讲述读取文本文件的方法分享,如下所示: 文本文件是最常用的文件格式之一, 下文是笔者…

    数据库 2023年6月11日
    096
  • Mysql学习

    显示字符集编码 mysql架构 逻辑架构 Client : 提供连接MySQL服务器功能的常用工具集 Server : MySQL实例,真正提供数据存储和数据处理功能的MySQL服…

    数据库 2023年6月16日
    059
  • 学习笔记——Django项目中的请求与响应

    2022-10-01 ALLOWED_HOSTS “ALLOWED_HOSTS”的含义: 它是指允许放在”ALLOWED_HOSTS&#8221…

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