Nested Loop Join(嵌套循环联结)
算法:
其思路相当的简单和直接:对于关系 R 的每个元组 r 将其与关系 S 的每个元组 s 在 JOIN 条件的字段上直接比较并筛选出符合条件的元组。
代价:
被联结的表所处内层或外层的顺序对磁盘 I/O 开销有着非常重要的影响。而 CPU 开销相对来说影响较小,主要是元组读入内存以后 (in-memory) 的开销,是 O (n * m)
对于 I/O 开销,根据 page-at-a-time 的前提条件,I/O cost = M + M * N,
翻译一下就是 I/O 的开销 = 读取 M 页的 I/O 开销 + M 次读取 N 页的 I/O 开销。
Sort-Merge Join(排序合并联结)
Nested Loop 一般在两个集合都很大的情况下效率就相当差了,而 Sort-Merge 在这种情况下就比它要高效不少,尤其是当两个集合的 JOIN 字段上都有聚集索引 (clustered index) 存在时,Sort-Merge 性能将达到最好。
算法:
基本思路也很简单 ( 复习一下数据结构中的合并排序吧 ),主要有两个步骤:
a. 按 JOIN 字段进行排序
b. 对两组已排序集合进行合并排序,从来源端各自取得数据列后加以比较 ( 需要根据是否在 JOIN 字段有重复值做特殊的“分区”处理 )
代价:(主要是 I/O 开销)
有两个因素左右 Sort-Merge 的开销:JOIN 字段是否已排序 以及 JOIN 字段上的重复值有多少。
◆最好情况下 ( 两列都已排序且至少有一列没有重复值 ):O (n + m) 只需要对两个集合各扫描一遍。(这里的 m,n 如果都能用到索引那就更好了)
◆最差情况下 ( 两列都未排序且两列上的所有值都相同 ):O (n * log n + m * log m + n * m) 两次排序以及一次全部元组间的笛卡尔乘积
Hash Join(哈希联结)
Hash Join 在本质上类似于两列都有重复值时的 Sort-Merge 的处理思想——分区 (patitioning)。但它们也有区别:Hash Join 通过哈希来分区 ( 每一个桶就是一个分区 ) 而 Sort-Merge 通过排序来分区 ( 每一个重复值就是一个分区 )。
值得注意的是,Hash Join 与上述两种算法之间的较大区别同时也是一个较大限制是它只能应用于等值联结 (equality join),这主要是由于哈希函数及其桶的确定性及无序性所导致的。
算法:
基本的 Hash Join 算法由以下两步组成:
同 nested loop,在执行计划中 build input 位于上方,probe input 位于下方。
hash join 操作分两个阶段完成:build(构造)阶段和 probe(探测)阶段。
a.Build Input Phase: 基于 JOIN 字段,使用哈希函数 h2 为较小的 S 集合构建内存中 (in-memory) 的哈希表,相同键值的以 linked list 组成一个桶 (bucket)
b.Probe Input Phase: 在较大的 R 集合上对哈希表进行核对以完成联结。
代价:
值得注意的是对于大集合 R 的每个元组 r ,hash bucket 中对应 r 的那个 bucket 中的每个元组都需要与 r 进行比较,这也是算法最耗时的地方所在。
CPU 开销是 O (m + n * b) b 是每个 bucket 的平均元组数量。
总结
三种 join 方法,都是拥有两个输入,优化的基本原则:
1. 避免大数据的 hash join,(hash join 适合低并发情况,他占用内存和 io 是很大的);
2. 尽量将其转化为高效的 merge join、nested loop join。可能使用的手段有表结构设计、索引调整设计、SQL 优化,以及业务设计优化。