SQL基础理论篇(七):多表关联的连接算法

文章目录

  • 简介
  • Nested Loops
  • Merge Join
  • Hash Join
  • 总结
  • 参考文献

简介

多表之间基础的关联算法一共有三种:

  • Hash Join
  • Nested Loops
  • Merge Join

还有很多基于这三种基础算法的变体,以Nested Loops为例,就有用于in和exist的半连接(Nested Loops Semi)算法,以及用于not in和not exists的反连接(Nested Loops Anti)算法等等。这里就不展开介绍了,有兴趣可以再专门搜搜。

Nested Loops

中文名叫嵌套循环,这种连接方式可以简单的用下图来展示:

SQL基础理论篇(七):多表关联的连接算法_第1张图片

上图来自参考文献2.

如果内表中的连接列有索引的话,内表就可以做index scan(索引扫描),否则的话,内表就只能做tab scan(全表扫描)了。

假设我们有一个外表outer, 一个内表inner,连接列c,嵌套循环实际上就是:

for i in outer:
	for j in inner:
        if i.c = i.c:
            # do something

这就是利用嵌套查询来做连接的全过程。

如果inner表的c列有索引,那内层循环就很快了,直接走index scan即可,如果没有,那就只能tab scan扫描全表了。如果没有索引的话,outer表有多少行记录,inner表就要 全表扫描多少次。(所以内层没有索引的话是很恐怖的)

至于外层outer表有没有索引,就无所谓了,反正他总是要做tab scan,取出每一行来跟inner表比较的。

有个小问题,嵌套循环中,如果连接条件使用了不等值连接,那内表还能走索引吗?

不等式!=会导致索引失效,使用函数会导致失效,但范围查询一般不会使索引失效,如<、>=之类的。(注意是一般情况, 如果优化器认为通过索引扫描的行记录数超过全表的10%~30%,那么它可能会自动变成全表扫描,放弃走索引

Merge Join

也称合并连接。

merge join实际上就是把两个有序队列(存在索引,或者无索引但是有序)进行连接,需要两端都要有序,所以不像Nested Join一样不断的循环内部的表。另外,Merge Join需要表连接条件中至少有一个等式,查询分析器才会去选择Merge Join。

merge join的过程可以用下面几个图来解释(同样来自参考文献2):

SQL基础理论篇(七):多表关联的连接算法_第2张图片

首先从两个表中各选取一行,比较,如果匹配,则返回匹配行;如果不匹配,则有较小值的表+1行,可认为是拥有较小值的表,指针后移一位。

SQL基础理论篇(七):多表关联的连接算法_第3张图片

但是如果当前两表并不是有序表(但存在索引),你需要显式的sort之后才能merge join的话,那么使用hash join是效率更高的选择。但也并不绝对,如果查询中已经存在了order by、group by、distinct等,那可能会导致查询分析器不得不进行显式排序,既然已经显式sort了,那查询分析器大概率会对sort之后的结果做merge join。

merge join 对不等值连接(除!=、like外)有更好的支持效率。

但是上面的这张图只能说明等式连接下的merge join的执行过程,那不等式连接下的merge join呢?

据说是这样的:

  1. 根据谓词条件访问A表,对A表的结果集按照连接列进行排序,形成R1;
  2. 根据谓词条件访问B表,对B表的结果集按照连接列进行排序,形成R2;
  3. 将R1和R2放入PGA(即SQL内存区);(这也是传说中的,sort merge join的两表只需要做一次扫描的原因,扫一次就写入内存)
  4. R1中取出第一条记录去R2进行匹配,然后取第二条、第三条,直到匹配完R1中的所有记录。由于R1和R2都在内存中,所以这个匹配过程也是在PGA中进行的。

可以发现,这个过程实际上跟嵌套循环很像,但是有个很显著的特点,merge join中内表是排过序的,这意味着,相比嵌套循环,merge join能高效的处理不等值连接

通常情况下,sort merge join并不适合OLTP的系统, 原因是对于OLTP来讲,排序是极其昂贵的操作

Hash Join

哈希连接。

哈希连接在针对大量、无序的数据时,性能均要优于Merge Join和Nested Join。对于连接列没有索引的情况,查询分析器会优先使用Hash Join。所以哈希连接是做大数据集连接时的常用方式。Hash Join只支持等值连接。

哈希连接分为两个阶段:生成和探测阶段

生成阶段,选择其中一个表(一般是量比较小的表)作为输入源,将其每一行都经过散列函数的计算,放入不同的Hash Bucket中。其中用到的Hash Function和Hash Bucket据说是黑盒的。另外,Hash Bucket中的数据无序。

这一阶段可以参照下图:

SQL基础理论篇(七):多表关联的连接算法_第4张图片

探测阶段,对于另一个表,同样针对每一行做散列函数计算,确定其应在的Hash Bucket,然后用这一行跟这个Hash Bucket中的每一行做匹配,匹配成功则返回对应的行。

探测阶段里,用到的表不需要放入内存,只需要把生成阶段里的小表放入内存即可。

由于Hash Join涉及散列函数计算,所以对CPU的消耗会很大,另外其输出的结果也是无序的。如果内存吃紧的话,还会涉及Grace哈希匹配和递归哈希匹配,可能会用到TempDB从而吃掉大量的IO(如果Hash表太大,无法一次构造在内存里,则会分成若干个partition,写入磁盘,所以使用时会多很多的IO)。有兴趣可以自行查找看看。

hash join 如何处理不等式连接问题?

hash join不支持不等值连接,即<>、<、>等,以及like。

事实上,我感觉也是,分成桶也没办法做范围查询之类的。

总结

SQL基础理论篇(七):多表关联的连接算法_第5张图片

出自参考文献2;

需要提一句的是,在MySQL8.0.18发布之前的很长一段时间里,Mysql的连接算法只有一个嵌套循环的变体,被人广为诟病。直到Mysql8.0.18发布后,Mysql才可以使用Hash Join了。

一般来讲,有索引的情况下会优先使用嵌套,没索引的等值连接则优先使用哈希连接。

参考文献

  1. 多表连接的三种方式详解 hash join、merge join、 nested loop
  2. 表的连接方式:NESTED LOOP、HASH JOIN、MERGE JOIN(修改) 写的非常好,推荐看看
  3. 表连接三剑客(嵌套循环连接,哈希连接,排序合并连接)
  4. 实现一个基于嵌套循环策略的两表连接算法_MySQL新特性之哈希连接

你可能感兴趣的:(#,SQL基础,sql,算法,数据库)