MySQL优化特性&Join算法

一、优化特性

1.1 ICP(Index Condition Pushdown)

Index First Key
  只是用来定位索引的起始范围,因此只在索引第一次Search Path(沿着索引B+树的根节点一直遍历,到索引正确的叶节点位置)时使用,一次判断即可。
Index Last Key
  用来定位索引的终止范围,因此对于起始范围之后读到的每一条索引记录,均需要判断是否已经超过了Index Last Key的范围,若超过,则当前查询结束。
Index Filter
  用于过滤索引查询范围中不满足查询条件的记录,因此对于索引范围中的每一条记录,均需要与Index Filter进行对比,若不满足Index Filter则直接丢弃,继续读取索引下一条记录;
Table Filter
  用于过滤通过前面索引的记录,此时的记录已经满足了Index First Key与Index Last Key构成的范围,并且满足Index Filter的条件,回表读取了完整的记录,判断完整记录是否满足Table Filter中的查询条件,同样的,若不满足,跳过当前记录,继续读取索引的下一条记录,若满足,则返回记录,此记录满足了where的所有条件,可以返回给前端用户。
  ICP是MySQL 5.6版本中的新特性,是一种在存储引擎层使用索引过滤数据的一种优化方式。server层负责sql的解析,执行; 引擎层去真正的做数据/索引的读写。以前是server层命令引擎层按index key把相应的数据从数据表读出,传给server层,然后server层来按where条件(index filter和table filter)做选择。而在MySQL 5.6加入ICP后,Index Filter与Table Filter分离,将Index Filter下推到引擎层进行过滤,如果不符合条件则无须读数据表,减少了回表与返回Server层的记录交互开销,节省了disk IO,提高了SQL的执行效率。

1.2 MRR(Multi-Range Read)

  MRR的使用过程:先通过二级索引取出满足条件的二级索引和主键放到缓冲区(大小由参数read_rnd_buffer_size控制)中,当二级索引扫描到文件的末尾或者缓冲区已满,则使用快速排序对缓冲区中的内容按照主键进行排序(由于此时表是顺序的,就将随机IO变成了顺序IO,多页数据记录可一次性读入或根据此次的主键范围分次读入,以减少IO操作,提高查询效率)。

MRR的优点
1.MRR使数据行能够按照索引元组顺序而不是随机被访问
2.MRR允许批量处理需要通过索引元组访问数据行的操作请求(如:范围索引扫描和连接属性索引等连接)

二、Join算法

2.1 SNLJ&INLJ

Simple Nested-Loops Join

  SNLJ就是两层循环全量扫描连接的两张表,得到符合条件的两条记录则输出,这也就是让两张表做笛卡尔积。

Index Nested-Loops Join

  INLJ是在SNLJ的基础上做了优化,通过连接条件确定可用的索引,在Index Loop中扫描索引而不去扫描数据本身,从而提高Index Loop的效率。

  而INLJ也有缺点,就是如果扫描的索引是非聚簇索引,并且需要访问非索引的数据,会产生一个回表读取数据的操作,这就多了一次随机的I/O操作。

2.2 BNL&BKA

Block Nested-Loops Join

  将外层循环的行/结果集存入join buffer, 内层循环的每一行与整个buffer中的记录做比较,从而减少内层循环的次数。主要用于当被join的表上无索引。

Batched Key Access

  当被join的表能够使用索引时,就先好顺序,然后再去检索被join的表。对这些行按照索引字段进行排序,因此减少了随机IO。如果被Join的表上没有索引,则使用老版本的BNL策略。

2.3 HASH JOIN

  在8.0.18之前,MySQL只支持Nest Loop Join算法,最简单的就是Simple NestLoop Join,MySQL针对这个算法做了若干优化,实现了Block Nest Loop Join,Index NestLoop Join和Batched Key Access等,有了这些优化,在一定程度上能缓解对HashJoin的迫切程度。
  Hash Join是针对等值join场景的优化,基本思想是,将外表数据加载到内存,并建立hash表,这样只需要遍历一遍内表,就可以完成join操作,输出匹配的记录。如果数据能全部加载到内存当然好,逻辑也简单,一般称这种join为CHJ(Classic Hash Join),之前MariaDB就已经实现了这种HashJoin算法。如果数据不能全部load到内存,就需要分批load进内存,然后分批join。

In-Memory Join(CHJ)

  Hash Join一般包括两个过程,创建hash表的build过程和探测hash表的probe过程。

1).build phase

  遍历外表,以join条件为key,查询需要的列作为value创建hash表。这里涉及到一个选择外表的依据,主要是评估参与join的两个表(结果集)的大小来判断,谁小就选择谁,这样有限的内存更容易放下hash表。

2).probe phase

  hash表build完成后,然后逐行遍历内表,对于内表的每个记录,对join条件计算hash值,并在hash表中查找,如果匹配,则输出,否则跳过。所有内表记录遍历完,则整个过程就结束了。
img img
On-Disk Hash Join

  CHJ的限制条件在于,要求内存能装下整个外表。在MySQL中,Join可以使用的内存通过参数join_buffer_size控制。如果join需要的内存超出了join_buffer_size,那么CHJ将无能为力,只能对外表分成若干段,每个分段逐一进行build过程,然后遍历内表对每个分段再进行一次probe过程。假设外表分成了N片,那么将扫描内表N次。这种方式当然是比较弱的。在MySQL8.0中,如果join需要内存超过了join_buffer_size,build阶段会首先利用hash算法将外表进行分区,并产生临时分片写到磁盘上;然后在probe阶段,对于内表使用同样的hash算法进行分区。由于使用分片hash函数相同,那么key相同(join条件相同)必然在同一个分片编号中。接下来,再对外表和内表中相同分片编号的数据进行CHJ的过程,所有分片的CHJ做完,整个join过程就结束了。这种算法的代价是,对外表和内表分别进行了两次读IO,一次写IO。
img img

img
Grace Hash Join (Oracle)
  MySQL的hash join当join_buffer_size不足时,MySQL会对外表进行分片,然后再进行CHJ过程。但是,极端情况下,如果数据分布不均匀,导致大量的数据hash后都分布在一个分桶中,导致分片后,join_buffer_size仍然不够,MySQL的处理方式是一次读分片读若干记录构建hash表,然后probe对应的外表分片。处理完一批后,清理hash表,重复上述过程,直到这个分片的所有数据处理完为止。这个过程与CHJ在join_buffer_size不足时,处理逻辑相同。

  GraceHash(Oracle)在遇到这种情况时,会继续分片进行二次Hash,直到内存足够放下一个hash表为止。但是,这里仍然有极端情况,如果输入join条件都相同,那么无论进行多少次Hash,都没法分开,那么这个时候GraceHashJoin也退化成和MySQL的处理方式一样。

hybrid hash join(Oceanbase)

  目前Oceanbase的HashJoin采用的是这种join方式,与Grace Hash Join的区别在于,如果缓存能缓存足够多的分片数据,会尽量缓存,那么就不必像GraceHash那样,严格地将所有分片都先读进内存,然后写到外存,然后再读进内存去走build过程。这个是在内存相对于分片比较充裕的情况下的一种优化,目的是为了减少磁盘的读写IO。

猜你喜欢

转载自blog.csdn.net/qq_42979842/article/details/107794862
今日推荐