对于我要优化的中等复杂查询,我注意到删除TOP n子句会更改执行计划。我猜想当查询包含TOP n时,数据库引擎将忽略TOP子句运行查询,然后最后仅将结果集缩减为所请求的n行。图形执行计划似乎表明情况是正确的-TOP是“最后”一步。但是看来还有更多事情要发生。

我的问题是,TOP n子句如何(以及为什么)影响查询的执行计划?

这是一个我的情况的简化版本:

查询正在匹配两个表A和B中的行。

如果没有TOP子句,优化器估计将是表A的19k行和表B的46k行。返回的实际行数是A的16k和B的13k。使用散列匹配将这两个结果集连接在一起,总共有69行(然后是应用)。该查询很快发生。

当我添加TOP 1001时,优化器不使用哈希匹配。取而代之的是,它首先对表A的结果进行排序(估计/实际为19k / 16k),然后对表B进行嵌套循环。表B的估计行数现在为1,奇怪的是TOP n直接影响B的估计执行次数(索引查找)-似乎始终为2n + 1,或者在我的情况下为2003。如果我更改TOP n,则此估计值也会相应更改。当然,由于这是一个嵌套联接,因此实际执行次数为16k(表A中的行数),这会使查询速度变慢。

实际情况要复杂一些,但这记录基本的想法/行为。使用索引查找来搜索两个表。这是SQL Server 2008 R2企业版。

评论

该查询具有ORDER BY子句。在计划中发生这种情况的地方添加TOP更改,但我更担心它如何影响针对表B的索引查找的执行次数...(当然,两者可能是相关的-我不知道)

相关讨论:FAST num_rows查询提示。

#1 楼


我猜想,当查询包含TOP n时,数据库引擎将忽略TOP子句运行查询,然后在
最后将结果集缩小到n请求
的行数。图形执行计划似乎表明是这种情况– TOP是“最后”一步。但是似乎还有更多的事情要进行。


上面的措辞使我觉得您可能对查询的执行方式有不正确的看法。查询计划中的运算符不是步骤(上一步的完整结果集由下一个步骤评估。

SQL Server使用流水线执行模型,其中每个运算符都公开Init之类的方法。 (),GetRow()和Close()。正如GetRow()名称所暗示的,操作员按需一次生成一行(根据其父操作员的要求),这在联机丛书的《逻辑和物理操作员》中有记录参考,并且在我的博客文章中更详细地解释了为什么查询计划向后运行。这种一次行模型对于形成合理的直觉来执行查询至关重要。


我的问题是,TOP n子句如何(以及为什么)影响查询的执行计划?


一些逻辑运算(例如TOP,半联接和FAST n查询提示)会影响查询优化器花费执行计划备选方案的方式。基本思想是,一个可能的计划形状可能比其他计划返回更快的前n行已优化以返回所有行的计划。

例如,索引嵌套循环联接通常是返回少量行的最快方法,尽管散列或合并联接与扫描在更大的情况下可能更有效套。查询优化器做出这些选择的原因是通过在操作的逻辑树中的特定点设置行目标。

行目标修改了查询计划替代方案的成本计算方式。它的本质是,优化器首先使每个运算符都付出成本,好像需要整个结果集一样,然后在适当的位置设置行目标,然后向下推回计划树,以估计期望检查的行数以满足行目标。例如,逻辑TOP(10)在逻辑查询树中的特定点将行目标设置为10。修改达到行目标的操作员成本,以估算他们需要生产多少行才能达到行目标。这种计算可能会变得很复杂,因此,通过一个完整的示例和带有注释的执行计划,可以更轻松地理解所有这些内容。行目标的影响不仅限于连接类型的选择,也不是扫描优先选择搜索和查找。与往常一样,基于行目标选择的执行计划将一如既往地受到优化器的推理能力和所提供信息的质量的约束。并非每个具有行目标的计划在实践中都会更快地产生所需的行数,但是根据成本模型,它会产生结果。

如果行目标计划证明不是更快,通常可以采用以下方法修改查询或向优化程序提供更好的信息,以使自然选择的计划最佳。哪种选择适合您,取决于具体的细节。行目标功能通常非常有效(尽管在并行执行计划中使用时需要注意一些错误)。

您的特定查询和计划可能不适用于此处的详细分析(所有人可以根据需要提供实际的执行计划),但希望此处概述的想法将使您取得进展。

#2 楼

使用TOP时,优化程序会发现减少工作量的机会。如果您要求10行,那么很有可能不需要消耗整个数据集。因此,可以将TOP运算符向右推得更远。它将继续从下一个运算符(在其右侧)请求行,直到接收到足够的行为止。

您指出,没有TOP,查询将在最后对数据进行排序。如果引擎可以提前知道连接将要满足多少行,则可以选择使用类似的计划,将TOP放在左侧。但是,由于进行散列匹配的工作量相对较高,并且可能没有合并联接的选择,因此优化器可能更喜欢将TOP进一步向右过滤。

查询表B时,它是一次获取一行。这就是为什么估算值是1的原因。它还假定只会在50%的时间内找到该行。因此,它猜测需要2n + 1次尝试才能找到它。

评论


估计行数会根据获取数据的方式而改变似乎并不正确。它如何获取数据不应影响基数。取而代之的方式上的变化将反映在执行次数上,对吗?

–大卫
2012年9月25日下午2:01

“估计的行数”是每次执行。在嵌套循环中,很可能执行不止一次。

–罗布·法利
2012年9月25日下午4:06

这与实际的行数和执行次数(实际)不同。如果实际计划显示了16,834次实际执行,并且返回了15,407条实际行,我认为这意味着它进行了16k次查找,但仅发现与谓词匹配的15k次。如果这意味着每次执行1.5万行,那将是15k * 16k = 2.4亿行-比表大10倍...

–大卫
2012年9月25日下午5:14

另外,我不确定我是否遵循您答案的最后声明。当您说2n + 1试图找到“它”时,“它”是什么意思?当然不是一行吗?您是否意味着优化程序假设A中的任何给定行都有50%的机会与B匹配,因此它需要从A中“尝试” 2003行以从B获得1001个匹配项? Microsoft是否在任何地方记录了此行为?它与TOP子句有什么关系?感谢您的答复/耐心等待。

–大卫
2012-09-25 5:23

是的,估计行是每次执行。实际行是总计。尽管操作员返回的行多于表中的行没有问题,因为很容易演示操作员多次返回同一行。

–罗布·法利
2012年9月25日上午7:09