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 tablesit 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记录。核心流程主要在p rocess_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的会支持更多并行算子,包括并行聚集,并行连接,并行分组以及并行排序等。
今天为大家准备了互联网面试必备的1到5年Java面试者都需要掌握的面试题,分别JVM,并发编程,MySQL,Tomcat,网络与IO及Spring系列等等,可以说掌握这些薪资涨10K还是可以的!
今天分享给大家的都是目前主流企业使用最高频的面试题库,也都是 Java 版本升级之后,重新整理归纳的最新答案,会让面试者少走很多不必要的弯路。同时每个专题都做到了详尽的面试解析文档,以确保每个阶段的读者都能看得懂。
Java虚拟机26题
-
-
JDK、 JRE、JVM 的关系是什么?
-
JVM 的内存模型以及分区情况和作用
-
JVM 对象创建步骤流程是什么?
-
垃圾回收算法有几种类型? 他们对应的优缺点又是什么?
-
简单介绍一下什么是类加载机制?
-
类的加载过程是什么?简单描述一下每个步骤
-
JVM 预定义的类加载器有哪几种?分别什么作用?
-
什么是双亲委派模式?有什么作用?
-
什么是 Class 文件? Class 文件主要的信息结构有哪些?
-
对象“对象已死” 是什么概念?
-
Java 语言怎么实现跨平台的?
-
JVM 数据运行区,哪些会造成 OOM 的情况?
-
详细介绍一下对象在分带内存区域的分配过程?
-
G1 与 CMS 两个垃圾收集器的对比
-
线上常用的 JVM 参数有哪些?
-
对象什么时候进入老年代?
-
什么是内存溢出, 内存泄露? 他们的区别是什么?
-
引起类加载操作的行为有哪些?
-
介绍一下 JVM 提供的常用工具
-
Full GC 、 Major GC 、Minor GC 之间区别?
-
什么时候触发 Full GC ?
-
什么情况下会出现栈溢出
-
说一下强引用、软引用、弱引用、虚引用以及他们之间和 gc 的关系
-
Eden 和 Survivor 的比例分配是什么情况?为什么?
-
CPU 资源占用过高怎么办
-
OOM 异常排查
同时里面还有我整理的JVM学习笔记和学习路线导图,很详尽的讲解了JVM的学习内容和实战笔记,有需要的朋友可观注下方公众号
内容分为五6个模块
-
-
JVM内存区 域划分
-
JVM执行子系统
-
垃圾回收器和内存分配策略
-
编写高效优雅Java程序
-
性能优化
-
JVM与性能优化学习笔记.Xmind
Java并发编程25题
-
-
Synchronized用过吗,其原理是什么?
-
你刚才提到获取对象的锁,这个“锁”到底是什么?如何确定对象的锁
-
什么是可重入性,为什么说Synchronized是可重入锁?
-
JVM对Java的原生锁做了哪些优化?
-
为什么说Synchronized是非公平锁?
-
什么是锁消除和锁粗化?
-
为什么说Synchronized是一个悲观锁? 乐观锁的实现原理又是什么?什么是CAS?
-
乐观锁一定就是好的吗?
-
跟Synchronized相比,可重入锁Reentrantl ock其实现原理有什么不同?
-
那么请谈谈AQS框架是怎么回事儿?
-
请尽可能详尽地对比下Synchronized和ReentrantLock的异同。
-
Reentrantl ock是如何实现可重入性的?
-
除了ReetrantLock, 你还接触过JUC中的哪些并发工具?
-
请谈谈ReadWriteLock和StampedLock如何让Java的线程彼此同步?你了解过哪些同步器?请分别介绍下
-
CyclicBarrier和CountDownLatch看起来很相似,请对比下呢?
对于并发编程这一块小编只展示了15题,分为5个模块,这些可以是大厂一面二面百分90%都问到的问题
MySQL数据库20题
目录展示
针对这20道高频问题,做出了很详细的解析,同时对MySQL及优化这一块而言,在这有一份笔记导图,里面每个节点都有笔记记载!
网络20题
-
-
HTTP 响应码有哪些?分别代表什么含义?
-
Forward 和 Redirect 的区别?
-
如何实现跨域?
-
说一下 JSONP 实现原理?
-
get 和 post 请求有哪些区别?
-
简述 TCP 和 UDP 的区别?
-
TCP 为什么要三次握手,两次不行吗?为什么?
-
说一下 TCP 粘包是怎么产生的?怎么解决粘包问题的?
-
TCP 如何保证可靠性
-
拥塞控制与流量控制的区别?
-
OSI 的七层模型都有哪些?
-
网络浏览器访问一个网址的整个过程?
-
解释滑动窗口算法
-
域名解析详细过程
-
IP 地址分为几类,每类都代表什么,私网是哪些?
-
计算机网络中的同步和异步
-
发现百度上不去,怎么办?
-
Cookie 和 Session 的区别?
-
HTTP 1.0 和 1.1 的区别?
-
HTTP 和 HTTPS 的主要区别?
Spring系列100题
-
-
什么是Spring框架? Spring框架有哪些主要模块?
-
使用Spring框架能带来哪些好处?
-
什么是控制反转(IOC)?什么是依赖注入?
-
请解释下Spring框架中的loC?
-
BeanFactory 和ApplicationContext有什么区别?
-
Spring 有几种配置方式?
-
如何用基于XML配置的方式配置Spring?
-
如何用基于Java配置的方式配置Spring?
-
怎样用注解的方式配置Spring?
-
请解释Spring Bean的生命周期?
-
Spring Bean的作用域之间有什么区别?
-
什么是Spring inner beans?
-
Spring 框架中的单例Beans是线程安全的么?
-
请举例说明如何在Spring中注入一个Java Collection?
-
如何向Spring Bean中注入一个Java.util.Properties?
-
请解释Spring Bean的自动装配?
-
请解释自动装配模式的区别?
-
如何开启基于注解的自动装配?
-
请举例解释@Required 注解?
-
请举例解释@ Autowired注解?
-
构造方法注入和设值注入有什么区别?
-
Spring 框架中有哪些不同类型的事件?
-
FileSystemResource 和ClassPathResource有何区别?
-
Spring 框架中都用到了哪些设计模式?
针对Spring系列问题只展示了24题,剩下的关于SpringBoot,Spring Cloud等问题也做出了很详细的解析,同时还有25节视频讲解Spring源码!
由于资料内容太多,平台篇幅限制,小编就展现了以上部分面试专题与资料,如需获取以下全部面试资料的同学,获取方式在下方公众号感谢配合与信任!
福利再附赠:498页Java架构进阶面试解析笔记内容涵盖包括(Java、MyBatis、ZooKeeper、Dubbo、Redis、MySQL、Spring、Spring Boot、Spring Cloud、RabbitMQ、Kafka、Linux 等等,)希望大家都能找到适合自己的公司,开开心心的撸代码。