浅谈数据库Join的实现原理

Join的实现算法有三种,分别是Nested Loops Join, Merge Join, Hash Join。
DB二、SQL Server和Oracle都是使用这三种方式,不过Oracle选择使用nested loop的条件跟SQL Server有点差异,内存管理机制跟SQL Server不同,所以查看执行计划,Oracle中nested loops运用很是多,而merge和hash方式相对较少,SQL Server中,merge跟hash方式则是很是广泛。
一.Nested Loopsb Join
1.定义
Nested Loops也称为嵌套迭代,它将一个联接输入用做外部输入表(显示为图形执行计划中的顶端输入),将另外一个联接输入用做内部(底端)输入表。外部循环逐行消耗外部输入表。内部循环为每一个外部行执行,在内部输入表中搜索匹配行。最简单的状况是,搜索时扫描整个表或索引;这称为单纯嵌套循环联接。若是搜索时使用索引,则称为索引嵌套循环联接。若是将索引生成为查询计划的一部分(并在查询完成后当即将索引破坏),则称为临时索引嵌套循环联接。伪码表示以下:算法

  for each row R1 in the outer table
      for each row R2 in the inner table
        if R1 joins with R2
            return (R1, R2)数据库

2.应用场景
适用于outer table(有的地方叫Master table)的记录集比较少(<10000)并且inner table(有的地方叫Detail table)索引选择性较好的状况下(inner table要有index)。
inner table被outer table驱动,outer table返回的每一行都要在inner table中检索到与之匹配的行。固然也能够用ORDERED 提示来改变CBO默认的驱动表,使用USE_NL(table_name1 table_name2)但是强制CBO 执行嵌套循环链接。服务器

   cost = outer access cost + (inner access cost * outer cardinality)数据结构

3.经常使用于执行的链接
Nested Loops常执行Inner Join(内部联接)、Left Outer Join(左外部联接)、Left Semi Join(左半部联接)和Left Anti Semi Join(左反半部联接)逻辑操做。
Nested Loops一般使用索引在内部表中搜索外部表的每一行。根据预计的开销,Microsoft SQL Server决定是否对外部输入进行排序来改变内部输入索引的搜索位置。
将基于所执行的逻辑操做返回全部知足 Argument 列内的(可选)谓词的行。
二.Merge Join
1.定义  
Merge Join第一个步骤是确保两个关联表都是按照关联的字段进行排序。若是关联字段有可用的索引,而且排序一致,则能够直接进行Merge Join操做;不然,SQL Server须要先对关联的表按照关联字段进行一次排序(就是说在Merge Join前的两个输入上,可能都须要执行一个Sort操做,再进行Merge Join)。 
两个表都按照关联字段排序好以后,Merge Join操做从每一个表取一条记录开始匹配,若是符合关联条件,则放入结果集中;不然,将关联字段值较小的记录抛弃,从这条记录对应的表中取下一条记录继续进行匹配,直到整个循环结束。
在多对多的关联表上执行Merge Join时,一般须要使用临时表进行操做。例如A join B使用Merge Join时,若是对于关联字段的某一组值,在A和B中都存在多条记录A一、A2...An、B一、B2...Bn,则为A中每一条记录A一、A2...An,都必须在B中对全部相等的记录B一、B2...Bn进行一次匹配。这样,指针须要屡次从B1移动到Bn,每一次都须要读取相应的B1...Bn记录。将B1...Bn的记录预先读出来放入内存临时表中,比从原数据页或磁盘读取要快。
2.应用场景另
用在数据没有索引可是已经排序的状况下。
一般状况下hash join的效果都比Sort merge join要好,然而若是行源已经被排过序,在执行排序合并链接时不须要再排序了,这时Sort merge join的性能会优于hash join。可使用USE_MERGE(table_name1 table_name2)来强制使用Sort merge join。
cost = (outer access cost * # of hash partitions) + inner access cost
3.经常使用于执行的链接
Merge Join常执行Inner Join(内部联接)、Left Outer Join(左外部联接)、Left Semi Join(左半部联接)、Left Anti Semi Join(左反半部联接)、Right Outer Join(右外部联接)、Right Semi Join(右半部联接)、Right Anti Semi Join(右反半部联接)和Union(联合)逻辑操做。
在 Argument 列中,若是操做执行一对多联接,则 Merge Join 运算符将包含 MERGE:() 谓词;若是操做执行多对多联接,则该运算符将包含 MANY-TO-MANY MERGE:() 谓词。Argument 列还包含一个用于执行操做的列的列表,该列表以逗号分隔。Merge Join 运算符要求在各自的列上对两个输入进行排序,这能够经过在查询计划中插入显式排序操做来实现。若是不须要显式排序(例如,若是数据库内有合适的 B 树索引或能够对多个操做(如合并联接和对汇总分组)使用排序顺序),则合并联接尤为有效。
三.Hash Join
1.定义
Hash Match有两个输入:build input(也叫作outer input)和probe input(也叫作inner input),不只用于inner/left/right join等,象union/group by等也会使用hash join进行操做,在group by中build input和probe input都是同一个记录集。
Hash Match操做分两个阶段完成:Build(构造)阶段和Probe(探测)阶段。
Build(构造)阶段主要构造哈希表(hash table)。在inner/left/right join等操做中,表的关联字段做为hash key;在group by操做中,group by的字段做为hash key;在union或其它一些去除重复记录的操做中,hash key包括全部的select字段。
Build操做从build input输入中取出每一行记录,将该行记录关联字段的值使用hash函数生成hash值,这个hash值对应到hash table中的hash buckets(哈希表目)。若是一个hash值对应到多个hash buckts,则这些hash buckets使用链表数据结构链接起来。当整个build input的table处理完毕后,build input中的全部记录都被hash table中的hash buckets引用/关联了。
Probe(探测)阶段,SQL Server从probe input输入中取出每一行记录,一样将该行记录关联字段的值,使用build阶段中相同的hash函数生成hash值,根据这个hash值,从build阶段构造的hash table中搜索对应的hash bucket。hash算法中为了解决冲突,hash bucket可能会连接到其它的hash bucket,probe动做会搜索整个冲突链上的hash bucket,以查找匹配的记录。 
若是build input记录数很是大,构建的hash table没法在内存中容纳时,SQL Server分别将build input和probe input切分红多个分区部分(partition),每一个partition都包括一个独立的、成对匹配的build input和probe input,这样就将一个大的hash join切分红多个独立、互相不影响的hash join,每个分区的hash join都可以在内存中完成。SQL Server将切分后的partition文件保存在磁盘上,每次装载一个分区的build input和probe input到内存中,进行一次hash join。这种hash join叫作Grace Hash join,使用的Grace Hash Join算法。
2.应用场景
适用于两个表的数据量差异很大。但须要注意的是:若是HASH表太大,没法一次构造在内存中,则分红若干个partition,写入磁盘的temporary segment,则会多一个I/O的代价,会下降效率,此时须要有较大的temporary segment从而尽可能提升I/O的性能。
能够用USE_HASH(table_name1 table_name2)提示来强制使用散列链接。若是使用散列连HASH_AREA_SIZE 初始化参数必须足够的大,若是是9i,Oracle建议使用SQL工做区自动管理,设置WORKAREA_SIZE_POLICY 为AUTO,而后调整PGA_AGGREGATE_TARGET 便可。
也可使用HASH_JOIN_ENABLED=FALSE(默认为TRUE)强制不使用hash join。
cost = (outer access cost * # of hash partitions) + inner access cost
3.经常使用于执行的连接
Hash Match运算符经过计算其生成输入中每行的哈希值生成哈希表。HASH:()谓词以及一个用于建立哈希值的列的列表出如今Argument列内。而后,该谓词为每一个探测行(若是适用)使用相同的哈希函数计算哈希值并在哈希表内查找匹配项。若是存在残留谓词(由 Argument 列中的 RESIDUAL:() 标识),则还须知足此残留谓词,只有这样行才能被视为是匹配项。行为取决于所执行的逻辑操做:
(1)对于联接,使用第一个(顶端)输入生成哈希表,使用第二个(底端)输入探测哈希表。按联接类型规定的模式输出匹配项(或不匹配项)。若是多个联接使用相同的联接列,这些操做将分组为一个哈希组。
(2)对于非重复或聚合运算符,使用输入生成哈希表(删除重复项并计算聚合表达式)。生成哈希表时,扫描该表并输出全部项。
(3)对于 union 运算符,使用第一个输入生成哈希表(删除重复项)。使用第二个输入(它必须没有重复项)探测哈希表,返回全部没有匹配项的行,而后扫描该哈希表并返回全部项。
四.性能分析
Hash join的主要资源消耗在于CPU(在内存中建立临时的hash表,并进行hash计算),而merge join的资源消耗主要在于磁盘I/O(扫描表或索引)。在并行系统中,hash join对CPU的消耗更加明显。因此在CPU紧张时,最好限制使用hash join。
在绝大多数状况下,hash join效率比其余join方式效率更高:
在Sort-Merge Join(SMJ),两张表的数据都须要先作排序,而后作merge。所以效率相对最差;
Nested-Loop Join(NL)效率比SMJ更高。特别是当驱动表的数据量很大(集的势高)时。这样能够并行扫描内表。
Hash join效率最高,由于只要对两张表扫描一次,Merge Join(合并联接)自己的速度很快,但若是须要排序操做,选择合并联接就会很是费时。然而,若是数据量很大且可以从现有 B 树索引中得到预排序的所需数据,则合并联接一般是最快的可用联接算法。若是是无序的数据,Merge Join首先作的是排序,若是数据量大,排序就会溢出到tempdb, 效率就将低了。
若是外部输入很小(<10000)而内部输入很大且预先建立了索引,则Nested Loops(嵌套循环联接)尤为有效。在许多小事务中(如那些只影响较小的一组行的事务),索引嵌套循环联接远比合并联接和哈希联接优越。但在大查询中,嵌套循环联接一般不是最佳选择。
若是两个表的数据量差异很大,则使用Hash Match。但须要注意的是:若是HASH表太大,没法一次构造在内存中,则分红若干个partition,写入磁盘的temporary segment,则会多一个I/O的代价,会下降效率,此时须要有较大的temporary segment从而尽可能提升I/O的性能。Hash join的主要资源消耗在于CPU(在内存中建立临时的HASH表,并进行HASH计算),而Merge join的资源消耗主要在于磁盘I/O(扫描表或索引)。
五.优化原则
1.如有单行谓词,则他的表必定是驱动表(select *  from employees e,departments d where e.department_id=d.department_id and e.department_id=100 and salary=10000;    上面的语句中e.department_id=d.department_id是链接谓词,e.department_id=100是非链接谓词(对链接列的限制),salary=10000是单行谓词(对非链接列的限制))
2.外链接时,必定是用显示的行数比较多的那个表做为驱动表。如:
select e.employee_id,e.department_id,d.manager_id,d.location_id from employees e right join departments d on e.department_id=d.department_id
则departments表显示的行数必定大于等于employees表,因此应该要以departments表做为驱动表,若是以employees表做为驱动表,则departments表中多显示的那几行就显示不出来了
4.通常状况下,Hash Join处理代价很是高,是数据库服务器内存和CPU的头号杀手之一,尤为是涉及到分区(数据量太大致使内存不够的状况,或者并发访问很高致使当前处理线程没法得到足够的内存,那么数据量不是特大的状况下也可能须要进行分区),为了尽快的完成全部的分区步骤,将使用大量异步的I/O操做,所以期间单一一个线程就可能致使多个磁盘驱动器出于忙碌状态,这颇有可能阻塞其它线程的执行。
5. 要避免大数据的Hash Join,尽可能将其转化为高效的Merge Join、Nested Loops。可能使用的手段有表结构设计、索引调整设计、SQL优化,以及业务设计优化。例如冗余字段的运用,将统计分析结果用service按期跑到静态表中,适当的冗余表,使用AOP或相似机制同步更新等。
6. 尽可能减小join两个输入端的数据量。这一点比较常犯的毛病是,条件不符合SARG((Searchable Arguments),在子查询内部条件给的不充分(SQL过于复杂状况下SQL Server查询优化器常常犯傻,写在子查询外部的条件不会被用在子查询内部,影响子查询内部的效率或者是跟子查询再join时候的效率)。
 并发

相关文章
相关标签/搜索