Join语句是非常常见的一类 SQL 语句,关于 Join 语句的分类及表现形式,属于 SQL 的基础知识,本文不做介绍,本文将向大家介绍 Join 操作在数据库引擎中的基本算法。

一、Join 运算的基本规则

介绍算法之前,让我们先看一下 Join 运算的基本规则。

对于两表 Join,通常都是先基于可以使用的筛选条件对参与 Join 操作的基表或视图进行过滤,之后再对两表进行 Join 操作,输出结果集。

两表 Join 的基本规则

图1 - 两表 Join 的基本规则

对于三表或多表 Join,则都是可以拆分为两表 Join 的方式进行处理,最先参与 Join 操作的两个表的 Join 的结果集,以表的形式参与后续的 Join 操作。

三表 Join 的基本规则

图2 - 三表/多表 Join 的基本规则

实际上,在多表 Join 的情况下,表与表之间的 Join 顺序对整个 SQL 语句的执行效率是有很大影响的,优化器会根据数据库中收集到的统计信息决定 Join 的顺序。

二、Join 运算的基本算法

Join 运算中可以包含多种连接条件,而等值连接条件则是最为常见的连接条件形式,本文以等值连接条件来对 Join 的实现进行说明。

由于无论有多少表参与连接,真正的连接都是由两表进行的,但在连接操作开始前,通常都会根据过滤条件对表的数据进行过滤,以减少参与连接的行数。这些经过过滤的表,在连接关系中没有特指的名称,通常称为左表和右表。在参与连接的左表与右表中,通常把包含数据较多的表称为大表,而包含数据较少的表称为小表。

1、嵌套循环连接

嵌套循环连接,Nested-Loop Join,简称NL Join,该种算法是最基本的 Join 算法。

在算法原理上,嵌套循环连接可以理解为常规的两重循环。具体的做法是:选定参与连接操作的一个表,对于表中的每一行,根据连接条件去匹配参与连接的另一个表中的每一行,能够匹配到的行加入到结果集中。

该算法的伪代码如下:

foreach Row ro in T1
  foreach Row ri in T2
    if (ri.JoinColValue == ro.JoinColValue)
      Add (ro.RowID, ri.RowID) to ResultSet;

对于嵌套循环连接,外层循环的表称为外侧表,对应的,内层循环的表则称为内侧表,而嵌套循环连接的两重循环则是由外侧表驱动进行的,因此,外侧表又称为驱动表。

对于嵌套循环连接,是在内侧表中查找与驱动表中指定的行所匹配的行,因此,如果内侧表的连接条件列上创建索引,则嵌套循环连接的效率会明显提升。也是基于同样的原因,如果是大表与小表进行嵌套循环连接,通常会选择小表为驱动表,这样外层循环的次数会减少,可以进一步提升连接操作的效率。

2、排序归并连接

排序归并连接,Sort Merge Join,是基于排序算法的一种连接算法,其算法核心是归并排序算法(Merge Sort)。

归并排序算法是一种典型的排序算法,其基本过程如下:

(1)把待排序的数据拆分为两段或多段;
(2)对拆分出的每段数据进行排序(可以使用任意排序算法);
(3)把步骤(2)排序后的数据段按顺序两两归并;
(4)输出排序结果。
归并排序算法

图3 - 归并排序算法

归并排序算法可以充分利用计算机的运算资源,以并行的方式快速完成排序。归并排序过程中,在数据拆分阶段,可以根据计算单元的数量进行拆分,通常拆分为2n段。

了解了归并排序算法,我们来看一下排序归并连接的算法过程:
(1)对参与连接的两表按连接条件列各自排序;
(2)对排序后的两表数据按连接条件列进行比较和归并;
(3)满足连接条件的两表数据添加到结果集中。
排序归并连接

图4 - 排序归并连接

如果参与连接的左表和右表都是大表,且在连接条件列上都有索引,则排序归并连接具有很高的执行效率。

3、哈希连接

哈希连接,Hash Join,是基于哈希算法(Hash)的一种连接算法,现代数据库中多数连接都使用这种算法。

哈希算法(Hash Algorithm),又称散列算法,是一种把任意长度的输入变换成固定长度的输出的算法,这是一种不可逆算法,通常用于对输入进行验证。

与哈希算法相关的几个概念:
(1)哈希函数(Hash Function):哈希算法通常通过一系列的函数运算来实现,所使用的函数称为哈希函数。
(2)哈希值(Hash Value):哈希算法的输出称为哈希值。
(3)哈希碰撞/哈希冲突(Hash Collision):对于某种特定的哈希函数,不同的输入可能有相同的输出(哈希值相同),这种情况称为哈希碰撞或哈希冲突。
(4)哈希桶(Hash Bucket):存在哈希碰撞的多个不同输入的集合,形象地称为“桶”,可以使用哈希值来标识这个桶。
(5)哈希倾斜(Hash Leaning):如果某个哈希值对应的输入很多,则该哈希值对应的哈希桶相比于其它桶就会很“深”,这种情况称为哈希倾斜。
(6)哈希表(Hash Table):这里哈希表与某些高级编程语言中的名为 HashTable 的数据结构类似,不同的哈希桶根据其对应的哈希值排列在一起形成哈希表。

常用的哈希算法有乘法哈希法、除法哈希法、斐波那契哈希法等,本文以最简单的求余运算来解释上面介绍的哈希算法及相关概念。
哈希算法及相关概念

图5 - 哈希算法及相关概念

哈希算法的一种应用是哈希匹配,本质上是一种查找算法。

哈希匹配的基本过程如下:
(1)对于查找集合,使用某个哈希函数构建哈希表;
(2)对于要查找的目标值,使用相同的哈希函数进行哈希运算,运算的结果(哈希值)必然指向步骤(1)中创建的哈希表中的某个哈希桶;
(3)在步骤(2)中定位的哈希桶中查找目标值。

比如,基于图5所示的原始数列与哈希表,查找12的过程如下图:
哈希匹配

图6 - 哈希匹配的过程

哈希连接的原理就是上面创建哈希表和进行哈希匹配两个步骤的结合,简单描述如下:
(1)Build Input
  <1> 确定哈希值,这个过程通常是由数据库内置的哈希函数完成的
  <2> 基于连接关系中的某个表的连接条件列创建哈希表,通常是基于小表创建
(2)Probe Input
  <1> 基于连接关系中的另外一个表的连接条件列的取值进行哈希匹配
  <2> 匹配到的记录添加到结果集
哈希连接

图7 - 哈希连接

哈希连接伪代码如下:

Initilize_HashBuckets() ;
CreateHashTable();

foreach ROW rd  in T1
{
    bucketNo=GetHash(rd.JoinColValue);
    HashBuckets[bucketNo].Items.Add(rd.JoinColValue) ;
}

foreach ROW r in T2
{
    bucketNo=GetHash(r.JoinColValue) ;
    if exists bucketNo
        foreach value in HashBuckets[bucketNo].Items
             if (value == r.JoinColValue)
             {
                  Add(value, r.JoinColValue) to ResultSet;
                  break;
             }
}

三、小结

(1)嵌套循环连接算法:对于驱动表中的每条记录,都需要遍历内侧表进行匹配,因此并不适用于大量数据参与连接的情况,尤其是内侧表的连接条件列没有索引的情况。
(2)排序归并连接算法:可以适用于量级相同的两个表(大表)进行 Join 的情况,算法的主要成本在于两表中连接条件列上的排序操作,而真正的连接操作步骤只需要扫描一次即可完成。
(3)哈希连接算法:对数据量不敏感,可适用于超大表连接场景,参与连接的两表分别仅需要扫描一次即可完成连接操作,效率较高。使用哈希连接时,尽量基于小表构建 HashTable,以尽量减少哈希碰撞。哈希连接算法的缺点是只适用于等值连接。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐