介绍在SQLServer中,我们经常看到的表与表之间的InnerJoin和OuterJoin,引擎会根据选择的列和数据上是否有索引来执行,将所选数据的选择性转换为三种物理连接之一:LoopJoin、MergeJoin和HashJoin。理解这三种物理连接是理解连接表时如何解决性能问题的基础。下面我分别介绍一下这三种连接的原理和适用场景。嵌套循环连接(NestedLoopJoin)循环嵌套连接是最基本的连接。顾名思义,需要循环嵌套。嵌套循环是三种方法中最支持不等式连接的方式。这种连接方式的流程可以简单的用下图表示:图1.循环嵌套连接的第一步图2.循环嵌套连接的第二步从以上两图不难看出,loopnestedconnectionsearch内循环表的次数等于外循环的行数,当外循环没有更多行时,循环嵌套结束。另外也可以看出,这种连接方式要求内层循环表是有序的(即有索引),外层循环表的行数要小于内层循环表的行数循环,否则查询分析器更倾向于HashJoin(本文后面会讨论)。从嵌套循环连接也可以看出,随着数据量的增加,这种方式的性能消耗会呈指数增长,所以当数据量达到一定程度时,查询分析器往往会采用这种方式。下面通过一个例子来看看循环嵌套连接,使用微软的AdventureWorks数据库:图3.一个简单的嵌套循环连接图3ProductID被索引,在循环的外层表(Product表)中符合ProductID的有4688个rows=870,因此对应的SalesOrderDetail表需要查找4688次。让我们考虑上述查询中的另一个示例,如图4所示。图4.额外列带来的额外书签查找从图4中可以看出,由于选择了额外的UnitPrice列,连接索引无法覆盖请求查询,必须通过书签搜索,这就是为什么我们需要养成只选择所需列的好习惯。为了解决上述问题,我们可以使用覆盖索引或者减少所需的列来避免书签查找。此外,只有5行与上面的ProductID匹配,因此查询分析器将选择书签搜索。如果我们增加符合条件的行数,查询分析器会倾向于扫描表(通常达到表行数的1%以上往往会进行表扫描而不是书签搜索,但这不是绝对的),如如图5所示。图5.查询分析器选择表扫描。可以看出此时查询分析器选择了表扫描连接。这种方法效率低很多,所以一个好的覆盖索引和Select*是需要注意的地方。另外,上面的情况即使涉及到表扫描,也是比较理想的情况。更糟糕的情况是,当使用多个不等式作为连接时,即使查询分析器知道每一列的统计分布,也不知道几个条件的条件。联合分布,导致执行计划错误,如图6。图6无法估计联合分布导致的偏差从图6可以看出,估计的行数和实际执行的行数存在巨大偏差实际行数,所以应该使用表扫描,但是查询分析器选择了书签查找,这种情况对性能的影响会比表扫描大。它有多大?我们可以通过强制表扫描将其与查询分析器的默认计划进行比较,如图7所示。图7.强制表扫描性能更好#p#MergeJoin(MergeJoin)当谈到合并连接时,我突然想起我晚上在西雅图的SQLPass峰会上在酒吧排队点酒。因为我和另一个哥们站错了位置,好像我们两个在插队,所以我赶紧说:对不起,我以为这里是队尾。对方都幽默地说:“没关系,在SQLServer里,我们叫mergejoin”。从上面的故事不难看出,MergeJoin其实就是把两个有序的队列连接起来,而且两端都需要有序,所以不需要像LoopJoin一样在循环里面不断的找表。其次,MergeJoin要求表连接条件中至少有一个等号查询分析器选择MergeJoin。MergeJoin的过程可以用下图简单描述:图8.MergeJoinMergeJoin的第一步是从两个输入集合中分别提取第一行,如果匹配则返回匹配的行。如果添加了两条不匹配的行,那么值较小的输入集+1,如图9所示。图9.值较小的输入集往下减1如果MergeJoin表示在C#代码中,它显示在代码1中。publicclassMergeJoin{//AssumethatleftandrightarealalreadysortedpublicstaticRelationSort(Relationleft,Relationright){Relationoutput=newRelation();while(!left.IsPastEnd()&&!right.IsPastEnd()){if(left.Key==right.Key){output.Add(left.Key);左.Advance();对.Advance();}elseif(left.Key
