在产品环境中,往往存在着大量的表连接情景,不管是inner join、outer join、cross join和full join(逻辑连接符号),在内部都会转化为物理连接(Physical Join),SQL Server共有三种物理连接:Nested Loop(嵌套循环),Merge Join(合并连接)和Hash Join(哈希连接)。这三个物理连接的处理方式不同,分别应用在不同的场景中。
在同一时刻,表连接只能是两表(或者是数据集,也就是表的一部分)之间的连接,通常按照表处于Join操作的位置来区分,把Join操作符前面的表叫做左表,把Join操作符后面的表叫做右表。如果有n个表连接,那么必须进行n-1次关联操作,上一次关联操作的结果作为下一次关联操作的一个数据集。On子句用于设置连接条件,可以决定连接的顺序。
一,嵌套循环嵌套循环是最基本的Join算法,分为两个循环,内部循环和外部循环,内部循环嵌套在外部循环内部。任何一个连接语句,都包含两个表,内部循环对应内部表,外部循环对应外部表。在图形执行计划中,上面的输入表是外部表,下面的输入表是内部表。
在嵌套循环中,外部循环逐行处理外部表,内部循环针对每一个外部行到内部表中进行查找,以找出所有匹配外部行的数据行。外部循环每输出一行,内部表中所有行都会和外部行进行匹配,假如外部表有N行,内部表有M行,对于外部表的每一行,内部表都会匹配M行,这种算法的开销是基于外部表的行数 乘以内部表的行数来确定,即以 N*M 来确定开销。
如果内部循环在连接条件上存在索引,那么把连接条件的每一个值叫做关联参数(Correlated Parameter),使用关联参数可以应用index seek算法去查找匹配行。
嵌套循环适用于:大表和小表进行连接,并且大表在连接条件上存在索引。当把外部表是小表,内部表是大表,且内部表是在连接列上有索引时,优化器倾向于使用嵌套循环。优化器对嵌套循环的优化是:
1,如果内部表在连接列上存在索引,那么优化器把外部表的输入行作为关联参数,基于关联参数对内部表使用index seek查询
2,对内部表使用Lazy Spool,Spool是指缓存中间结果集,以便重用。当内部表在连接条件上有很多重复值,但只有少数行可以和外部行匹配时,Nested Loop算法的效率非常高。
不是所有的Join都有相关参数,如果没有合适的索引,或者on子句没有指定合适的连接条件,就不会使用index seek算法,也就不会有相关参数,通常情况下,inner join和left join 可以有相关参数,而right join,cross join和full join 没有相关参数。
二,合并连接合并连接使用的是合并算法,要求至少存在一个相等的连接谓词(on子句中至少存在一个相等条件),并且两个输入在合并列上必须是有序的。通常情况下,查询优化器会扫描表上的索引,以检查是否有索引包含合并列;如果没有适当的索引,那么合并连接的后面使用sort操作符对合并列进行排序,索引和Sort操作符都是为了满足“合并算法对两个输入必须是有序的”这一硬性要求。合并连接,也称作排序-合并-连接(Sort-Merge-Join)。
如果合并连接需要对数据集进行排序,那么还需要为Sort操作符向系统申请授予内存(Granted Memory),以存储排序的中间结果集。如果查询语句输出的结果在连接键上是有序,order by join-keys,那么连接查询倾向于使用合并连接,如果排序是不必要的,那么可以去掉order by子句。
由于两个输入都已经排序,Merge Join操作符从每一输入各获取一行进行比较,例如,对于inner join,如果两行相等,则放回这个相等的结果;如果不相等,则废弃值较小的行,并从该输入中获取下一行。这一过程重复进行,直到处理完所有的数据行。
如果两个输入是有序的数据,那么Merge Join的执行速度很快,但是,当需要添加Sort操作符对输入数据进行排序时,选择使用Merge Join操作符将十分费时。对于数据量大,并且可以从B-Tree索引中获得已排序的数据,那么Merge Join是执行速度最快的连接算法。
三,哈希连接哈希连接(Hash Join)适用于无序的大表之间的连接,有两个输入:生成输入(Build Input) 和 探测输入(Porbe Input),查询优化器使用两个输入中较小的作为生成输入。哈希连接扫描生成输入,加载到内存中,生成哈希表,这个过程叫做生成阶段(build phrase)。当生成输入的所有行都加载到Hash 表之后,开始探测阶段(probe phrase):对于探测输入的每一行,计算其Hash值,和哈希表进行匹配,输出匹配的数据行。在探测阶段,每做一次哈希连接操作,探测输入的每一行只会扫描一次,再根据哈希键值去扫描整个哈希表。