MySQL8.0新特性学习笔记(四):Hash Join

目录

准备工作一:驱动表和被驱动表

准备工作二:MySQL8.0之前的连接方式

Hash Join


MySQL8.0正式引入了Hash Join的连接方式,下面介绍一下这种连接方式,并且和之前的连接方式做一下对比。

 

准备工作一:驱动表和被驱动表

比如这样一个使用了连接的sql:

SELECT * from tableA join tableB on tableA.code=tableB.code;

tableA和tableB通过code字段进行关联,一般情况下,MySQL的查询优化器会先选择其中一个表作为驱动表,假设选择了表A,那么表A是驱动表,驱动表的作用就是,遍历驱动表的每一条记录,对于某条记录中的code,从另外一个表B中查询对应的记录,然后把两条记录做关联,那么表B就是被驱动表。

实际情况中查询优化器会按照自己的逻辑选择驱动表和被驱动表。

 

准备工作二:MySQL8.0之前的连接方式

在MySQL8.0引入Hash Join之前,使用的是经典的Nested Loops Join(NLJ)算法,嵌套循环算法。

Nested Loops Join有以下几种演进版本:

1,Simple Nested Loops Join(SNLJ),简单嵌套循环算法

连接双方在连接字段上都没有索引,所以此算法没有什么套路,从驱动表取出记录,按照join的条件在被驱动表中查询,找到可连接的记录,把数据连接并返回。

时间复杂度:O(N2)

显然效率不是很高。

查询优化器考虑选择记录少的表作为驱动表。

2,Index Nested Loops Join(INLJ),索引嵌套循环连接​​​​

有一方在连接字段上有索引,这种场景在MySQL的使用中见的比较多。

优化器会考虑选择有索引的一方作为被驱动表,双方都有索引则选择索引高度低的,索引高度一样则选择记录数多的作为被驱动表,对于驱动表的每一条记录,在被驱动表中使用索引查询,大大减少了比较次数,提高了查询效率。

索引是主键时效率更高。

3,Block Nested Loops Join(BNLJ),块嵌套循环连接

可能人们觉得索引嵌套循环连接的效率还可以再优化一下,出发点在于每次试图匹配被驱动表的数据时,都要把被驱动表加载到内存,所以,为了减少被驱动表的加载次数,为了每次加载被驱动表时可以尽量多的匹配驱动表数据,块嵌套循环连接出现了。

 

原理:

块嵌套循环连接设定了一块缓存区域(Join Buffer),存放驱动表相关数据,包括查询结果要用的字段,查询条件中用到的字段,和连接被驱动表的字段,如果此缓存区域足够大,能够包括驱动表所有数据,那么查询时驱动表和被驱动表都只需要扫描一次,就能得到最终的匹配结果,极大提高了查询效率。

块嵌套循环连接的原理也告诉我们,平时尽量不要用select *这种查询,可以提高缓冲区的使用效率。

缓冲区大小可以使用系统变量Join_buffer_size来调整。

 

优化:Regular join buffer和Incremental join buffer

在此基础上,MySQL对这套逻辑又做了一点优化,把缓存区域Join Buffer分成两种,原来逻辑下的缓冲区叫Regular join buffer,又加了一种增量缓冲区Incremental join buffer。

举个例子:当sql中有三个表相互关联时,比如表A表B表C三表关联,那么MySQL会使用两个缓冲区,第一个缓冲区存放表A的相关字段(用来和表B关联),第二个缓冲区存放表A和表B完成关联后的相关字段(用来和表C关联),那么第二个缓冲区实际存放的信息将会是表B的相关字段,加上一个指针,指向第一个缓冲区中表A的对应字段。在此场景下,第一个缓冲区就是Regular join buffer,第二个缓冲区属于Incremental join buffer。

这种优化方式提高了缓冲区内存的利用率,从而提高了查询效率。

块嵌套循环连接的原理和Hash Join就很像了。

 

4,Batched key Access Join,批量键访问连接

批量键访问连接基于MySQL5.6的一种新特性:MRR(multi range read)。当被驱动表的链接字段有非主键索引时,而是通过范围扫描读取一部分记录放入内存中,然后按照主键排序,这样匹配到数据后需要按对应的主键索引去查询被驱动表的真实数据时,可以按照排好序的主键进行顺序访问,因为InnoDB叶子节点的数据也是按主键排序的,所以这种读取方式能提高查询效率。

要打开MRR,需要打开系统变量optimizer_switch中的mrr和mrr_cost_based选项。

MRR的使用流程中用到了排序,有一定的开销,有些sql中效率可能没有那么高。

 

Hash Join

终于来到了Hash Join,Hash Join可以在被驱动表没有索引的情况下进行快速的连接并查询。

原理:

1,Hash Join首先使用了Join Buffer,把驱动表相关字段存入内存。这一步和块嵌套循环连接套路相同。

2,把Join Buffer中对应的字段值生成一个散列表,保存在内存中。这一步叫build。

3,扫描被驱动表,对被驱动表中的相关字段进行散列并比较。这一步叫probe。

可见,Hash Join也依赖Join Buffer,在最好的场景下,如果Join Buffer能覆盖驱动表所有相关字段,那么在查询的过程中驱动表和被驱动表都只需要扫描一次,如果散列算法够好,比较次数也只是被驱动表的记录数。

Hash Join只能用于等值连接。

大表连接Hash Join的优化效果比较明显。

要使用Hash Join,需要把系统变量join_cache_level设为大于等于4的值,并且把optimizer_switch的join_cache_hashed设为on。

 

总结:

1,驱动表数据量应该尽量小。

2,被驱动表查询效率尽量高,比如可以建立索引。

3,正确的配置相关参数,可以极大的提高查询效率。

 

 

MySQL8.0全部学习笔记:

MySQL8.0新特性学习笔记(一):binlog复制策略优化

MySQL8.0新特性学习笔记(二):窗口函数

MySQL8.0新特性学习笔记(三):直方图

MySQL8.0新特性学习笔记(四):Hash Join

MySQL8.0新特性学习笔记(五):JSON格式简介和JSON函数详解

MySQL8.0新特性学习笔记(六):新特性介绍

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章