浅谈SQL Server中的三种物理链接操做

简介

    在SQL Server中,咱们所常见的表与表之间的Inner Join,Outer Join都会被执行引擎根据所选的列,数据上是否有索引,所选数据的选择性转化为Loop Join,Merge Join,Hash Join这三种物理链接中的一种。理解这三种物理链接是理解在表链接时解决性能问题的基础,下面我来对这三种链接的原理,适用场景进行描述。html

 

嵌套循环链接(Nested Loop Join)

    循环嵌套链接是最基本的链接,正如其名所示那样,须要进行循环嵌套,这种链接方式的过程能够简单的用下图展现:算法

    1

     图1.循环嵌套链接的第一步数据库

     2

     图2.循环嵌套链接的第二步函数

 

    由上面两个图不难看出,循环嵌套链接查找内部循环表的次数等于外部循环的行数,当外部循环没有更多的行时,循环嵌套结束。另外,还能够看出,这种链接方式须要内部循环的表有序(也就是有索引),而且外部循环表的行数要小于内部循环的行数,不然查询分析器就更倾向于Hash Join(会在本文后面讲到)。oop

    经过嵌套循环链接也能够看出,随着数据量的增加这种方式对性能的消耗将呈现出指数级别的增加,因此数据量到必定程度时,查询分析器每每就会采用这种方式。性能

    下面咱们经过例子来看一下循环嵌套链接,利用微软的AdventureWorks数据库:优化

    3

    图3.一个简单的嵌套循环链接spa

   

    图3中ProductID是有索引的,而且在循环的外部表中(Product表)符合ProductID=870的行有4688条,所以,对应的SalesOrderDetail表须要查找4688次。让咱们在上面的查询中再考虑另一个例子,如图4所示。3d

    4

    图4.额外的列带来的额外的书签查找htm

  

    由图4中能够看出,因为多选择了一个UnitPrice列,致使了链接的索引没法覆盖所求查询,必须经过书签查找来进行,这也是为何咱们要养成只Select须要的列的好习惯,为了解决上面的问题,咱们既能够用覆盖索引,也能够减小所需的列来避免书签查找。另外,上面符合ProductID的行仅仅只有5条,因此查询分析器会选择书签查找,假如咱们将符合条件的行进行增大,查询分析器会倾向于表扫描(一般来讲达到表中行数的1%以上每每就会进行table scan而不是书签查找,但这并不绝对),如图5所示。

    5

    图5.查询分析器选择了表扫描

 

    能够看出,查询分析器此时选择了表扫描来进行链接,这种方式效率要低下不少,所以好的覆盖索引和Select *都是须要注意的地方。另外,上面状况即便涉及到表扫描,依然是比较理想的状况,更糟糕的状况是使用多个不等式做为链接时,查询分析器即便知道每个列的统计分布,但殊不知道几个条件的联合分布,从而产生错误的执行计划,如图6所示。

    6

    图6.因为没法预估联合分布,致使的误差

 

   由图6中,咱们能够看出,估计的行数和实际的行数存在巨大的误差,从而应该使用表扫描但查询分析器选择了书签查找,这种状况对性能的影响将会比表扫描更加巨大。具体大到什么程度呢?咱们能够经过强制表扫描和查询分析器的默认计划进行比对,如图7所示。

    7

    图7.强制表扫描性能反而更好

 

合并链接(Merge Join)

    谈到合并链接,我忽然想起在西雅图参加SQL Pass峰会晚上酒吧排队点酒,因为我和另一哥们站错了位置,貌似咱们两个在插队同样,我赶忙说:I’m sorry,i thought here is end of line。对方无不幽默的说:”It’s OK,In SQL Server,We called it merge join”。

    由上面的小故事不难看出,Merge Join其实上就是将两个有序队列进行链接,须要两端都已经有序,因此没必要像Loop Join那样不断的查找循环内部的表。其次,Merge Join须要表链接条件中至少有一个等号查询分析器才会去选择Merge Join。

    Merge Join的过程咱们能够简单用下面图进行描述:

    8

    图8.Merge Join第一步

 

    Merge Join首先从两个输入集合中各取第一行,若是匹配,则返回匹配行。假如两行不匹配,则有较小值的输入集合+1,如图9所示。

    9

    图9.更小值的输入集合向下进1

    用C#代码表示Merge Join的话如代码1所示。

public class MergeJoin
{
    // Assume that left and right are already sorted
    public static Relation Sort(Relation left, Relation right)
    {
        Relation output = new Relation();
        while (!left.IsPastEnd() && !right.IsPastEnd())
        {
            if (left.Key == right.Key)
            {
                output.Add(left.Key);
                left.Advance();
                right.Advance();
            }
            else if (left.Key < right.Key)
                left.Advance();
            else //(left.Key > right.Key)
                right.Advance();
        }
        return output;
    }
}

 

    代码1.Merge Join的C#代码表示

    所以,一般来讲Merge Join若是输入两端有序,则Merge Join效率会很是高,可是若是须要使用显式Sort来保证有序实现Merge Join的话,那么Hash Join将会是效率更高的选择。可是也有一种例外,那就是查询中存在order by,group by,distinct等可能致使查询分析器不得不进行显式排序,那么对于查询分析器来讲,反正都已经进行显式Sort了,何不一石二鸟的直接利用Sort后的结果进行成本更小的MERGE JOIN?在这种状况下,Merge Join将会是更好的选择。

    另外,咱们能够由Merge Join的原理看出,当链接条件为不等式(但不包括!=),好比说> < >=等方式时,Merge Join有着更好的效率。

    下面咱们来看一个简单的Merge Join,这个Merge Join是由汇集索引和非汇集索引来保证Merge Join的两端有序,如图10所示。

    10

    图10.由汇集索引和非汇集索引保证输入两端有序

 

    固然,当Order By,Group By时查询分析器不得不用显式Sort,从而能够一举两得时,也会选择Merge Join而不是Hash Join,如图11所示。

    11

    图11.一举两得的Merge Join

 

哈希匹配(Hash Join)

    哈希匹配链接相对前面两种方式更加复杂一些,可是哈希匹配对于大量数据,而且无序的状况下性能均好于Merge Join和Loop Join。对于链接列没有排序的状况下(也就是没有索引),查询分析器会倾向于使用Hash Join。

    哈希匹配分为两个阶段,分别为生成和探测阶段,首先是生成阶段,第一阶段生成阶段具体的过程能够如图12所示。

    12

    图12.哈希匹配的第一阶段

 

    图12中,将输入源中的每个条目通过散列函数的计算都放到不一样的Hash Bucket中,其中Hash Function的选择和Hash Bucket的数量都是黑盒,微软并无公布具体的算法,但我相信已是很是好的算法了。另外在Hash Bucket以内的条目是无序的。一般来说,查询优化器都会使用链接两端中比较小的哪一个输入集来做为第一阶段的输入源。

    接下来是探测阶段,对于另外一个输入集合,一样针对每一行进行散列函数,肯定其所应在的Hash Bucket,在针对这行和对应Hash Bucket中的每一行进行匹配,若是匹配则返回对应的行。

    经过了解哈希匹配的原理不难看出,哈希匹配涉及到散列函数,因此对CPU的消耗会很是高,此外,在Hash Bucket中的行是无序的,因此输出结果也是无序的。图13是一个典型的哈希匹配,其中查询分析器使用了表数据量比较小的Product表做为生成,而使用数据量大的SalesOrderDetail表做为探测。

    13

    图13.一个典型的哈希匹配链接

 

    上面的状况都是内存能够容纳下生成阶段所需的内存,若是内存吃紧,则还会涉及到Grace哈希匹配和递归哈希匹配,这就可能会用到TempDB从而吃掉大量的IO。这里就不细说了,有兴趣的同窗能够移步:http://msdn.microsoft.com/zh-cn/library/aa178403(v=SQL.80).aspx

 

总结

    下面咱们经过一个表格简单总结这几种链接方式的消耗和使用场景:

  嵌套循环链接 合并链接 哈希链接
适用场景 外层循环小,内存循环条件列有序 输入两端都有序 数据量大,且没有索引
CPU 低(若是没有显式排序)
内存 低(若是没有显式排序)
IO 可能高可能低 可能高可能低

    理解SQL Server这几种物理链接方式对于性能调优来讲必不可少,不少时候当筛选条件多表链接多时,查询分析器就可能不是那么智能了,所以理解这几种链接方式对于定位问题变得尤其重要。此外,咱们也能够经过从业务角度减小查询范围来减小低下性能链接的可能性。

 

 

参考文献:

http://msdn.microsoft.com/zh-cn/library/aa178403(v=SQL.80).aspx

http://www.dbsophic.com/SQL-Server-Articles/physical-join-operators-merge-operator.html

相关文章
相关标签/搜索