为什么添加 TOP 1 会显着降低性能?

Ken*_*her 41 performance sql-server sql-server-2008-r2 query-performance performance-tuning

我有一个相当简单的查询

SELECT TOP 1 dc.DOCUMENT_ID,
        dc.COPIES,
        dc.REQUESTOR,
        dc.D_ID,
        cj.FILE_NUMBER
FROM DOCUMENT_QUEUE dc
JOIN CORRESPONDENCE_JOURNAL cj
    ON dc.DOCUMENT_ID = cj.DOCUMENT_ID
WHERE dc.QUEUE_DATE <= GETDATE()
  AND dc.PRINT_LOCATION = 2
ORDER BY cj.FILE_NUMBER
Run Code Online (Sandbox Code Playgroud)

这给了我可怕的表现(就像从不费心等待它完成一样)。查询计划如下所示:

在此处输入图片说明

但是,如果我删除它,TOP 1我会得到一个看起来像这样的计划,它会在 1-2 秒内运行:

在此处输入图片说明

下面更正 PK 和索引。

TOP 1更改查询计划这一事实并不让我感到惊讶,我只是有点惊讶它使情况变得更糟。

注意:我已经阅读了这篇文章的结果并理解了 aRow Goal等的概念。我很好奇的是如何更改查询以使其使用更好的计划。目前我正在将数据转储到临时表中,然后从中取出第一行。我想知道是否有更好的方法。

编辑对于事后阅读本文的人,这里有一些额外的信息。

  • Document_Queue - PK/CI 是 D_ID,它有大约 5k 行。
  • Correspondence_Journal - PK/CI 是 FILE_NUMBER,CORRESPONDENCE_ID,它有大约 140 万行。

当我开始时,没有其他索引。我在 Correspondence_Journal (Document_Id, File_Number) 上找到了一个

Dan*_*her 30

既然你得到了正确的计划ORDER BY,也许你可以推出自己的TOP运营商?

SELECT DOCUMENT_ID, COPIES, REQUESTOR, D_ID, FILE_NUMBER
FROM (
    SELECT dc.DOCUMENT_ID,
           dc.COPIES,
           dc.REQUESTOR,
           dc.D_ID,
           cj.FILE_NUMBER,
           ROW_NUMBER() OVER (ORDER BY cj.FILE_NUMBER) AS _rownum
    FROM DOCUMENT_QUEUE dc
    INNER JOIN CORRESPONDENCE_JOURNAL cj
        ON dc.DOCUMENT_ID = cj.DOCUMENT_ID
    WHERE dc.QUEUE_DATE <= GETDATE()
      AND dc.PRINT_LOCATION = 2
) AS sub
WHERE _rownum=1;
Run Code Online (Sandbox Code Playgroud)

在我看来,上面的查询计划ROW_NUMBER()应该和你有一个ORDER BY. 查询计划现在应该有一个 Segment、Sequence Project 和最后一个 Filter 运算符,其余的应该看起来就像你的好计划。

  • @KennethFisher,飞盘的答案更简单,但是大锤比标准框架锤更简单地驱动完成钉子的方式。它还伴随着很大的风险,特别是如果长期留在原地。除了在测试中,或者可能是边缘例外,我不会使用这样的提示。 (10认同)
  • @KennethFisher Imo,查询提示的*主要*风险是,随着数据的增长或变化,您执行的查询计划可能会变得比系统自己发现的更糟糕。您已经看到计划中的一个小错误如何严重影响性能。在生产中使用提示声明,“我知道这个计划将永远,*永远*是最好的,因为我完全了解计划器以及我的数据在生产中这个查询的生命周期内的行为方式。” 我从未对查询如此自信。 (5认同)
  • 实际上,虽然它确实给了顶级操作符(以及一堆其他东西(序列项目、段和排序)),但它仍然运行在亚秒级。我要给@frisbee 正确答案,因为他是第一个而且更简单。很好的答案。 (3认同)

pap*_*zzo 29

尝试强制散列连接*

SELECT TOP 1 
       dc.DOCUMENT_ID,
       dc.COPIES,
       dc.REQUESTOR,
       dc.D_ID,
       cj.FILE_NUMBER
FROM DOCUMENT_QUEUE dc
INNER HASH JOIN CORRESPONDENCE_JOURNAL cj
        ON dc.DOCUMENT_ID = cj.DOCUMENT_ID
       AND dc.QUEUE_DATE <= GETDATE()
       AND dc.PRINT_LOCATION = 2
ORDER BY cj.FILE_NUMBER
Run Code Online (Sandbox Code Playgroud)

优化器可能认为循环使用 top 1 会更好,这很有意义,但实际上它在这里不起作用。这里只是一个猜测,但可能该线轴的估计成本已关闭 - 它使用 TEMPDB - 您的 TEMPDB 性能可能很差。


* 注意连接提示,因为它们强制计划表访问顺序与查询中表的写入顺序相匹配(就像OPTION (FORCE ORDER)已指定一样)。从文档链接:

BOL提取物

这可能不会在示例中产生任何不良影响,但总的来说,它很可能会产生。FORCE ORDER(暗示或明确)是一个非常强大的提示,超越了强制执行的顺序;它阻止了广泛的优化器技术的应用,包括部分聚合和重新排序。

一个OPTION (HASH JOIN) 查询提示可以是在合适的情况下较少干扰,因为这并不暗示FORCE ORDER。但是,它确实适用于查询中的所有连接。可以使用其他解决方案。

  • 在强制散列连接时,您是在强制扫描大表。有更好的选择。 (4认同)
  • 不确定我喜欢这个答案。加入提示非常具有侵入性。应该首先尝试一些简单的索引更改,例如日期列上的索引。 (3认同)

Rob*_*ley 29

编辑:+1 在这种情况下有效,因为事实证明这FILE_NUMBER是整数的零填充字符串版本。对于字符串,这里更好的解决方案是追加''(空字符串),因为追加值会影响顺序,或者为数字添加常量但包含非确定性函数的内容,例如sign(rand()+1). “打破排序”的想法在这里仍然有效,只是我的方法不理想。

+1

不,我并不是说我同意任何事情,我的意思是作为一种解决方案。如果您将查询更改为ORDER BY cj.FILE_NUMBER + 1TOP 1则行为会有所不同。

您会看到,在为有序查询设置小行目标后,系统将尝试按顺序使用数据,以避免使用 Sort 运算符。它还将避免构建哈希表,认为它可能不需要做太多工作来找到第一行。在您的情况下,这是错误的 - 从这些箭头的粗细来看,似乎必须消耗大量数据才能找到单个匹配项。

这些箭头的粗细表明您的DOCUMENT_QUEUE(DQ) 桌子比您的CORRESPONDENCE_JOURNAL(CJ) 桌子小得多。最好的计划实际上是检查 DQ 行,直到找到 CJ 行。事实上,如果查询优化器 (QO) 没有这种讨厌ORDER BY的东西,它就会这样做,CJ 上的覆盖索引很好地支持了这一点。

所以如果你ORDER BY完全放弃了,我希望你会得到一个涉及嵌套循环的计划,迭代 DQ 中的行,寻找 CJ 以确保该行存在。使用TOP 1,这将在拉出一行后停止。

但是,如果您确实需要按FILE_NUMBER顺序排列第一行,那么您可以欺骗系统忽略似乎(错误地)非常有用的索引,这样做ORDER BY CJ.FILE_NUMBER+1- 我们知道这将保持与以前相同的顺序,但重要的是 QO没有。QO 将专注于得到整个集合,以便可以满足 Top N Sort 运算符。此方法应生成一个计划,其中包含一个计算标量运算符来计算排序值,以及一个 Top N Sort 运算符来获取第一行。但是在这些的右边,你应该看到一个很好的嵌套循环,在 CJ 上做了很多搜索。并且比运行与 DQ 中的任何内容都不匹配的大行表更好的性能。

哈希匹配不一定很糟糕,但是如果您从 DQ 返回的行集比 CJ 小得多(正如我所期望的那样),那么哈希匹配将扫描更多的 CJ比它需要。

注意:我使用了 +1 而不是 +0,因为查询优化器很可能会识别出 +0 没有任何改变。当然,同样的事情可能适用于 +1,如果不是现在,那么在将来的某个时候。


Mar*_*ith 7

我已经阅读了这篇文章的结果并了解了 Row Goal 等的概念。我很好奇的是如何更改查询以便它使用更好的计划

添加仅OPTION (QUERYTRACEON 4138)关闭该查询的行目标的影响,而不会对最终计划过于规范,并且可能是最简单/最直接的方法。

如果添加此提示会给您带来权限错误(对于 而言是必需的DBCC TRACEON),您可以使用计划指南来应用它:

使用QUERYTRACEON在计划指南spaghettidba

...或者只是使用存储过程:

QUERYTRACEON需要什么权限?通过肯德拉·利特尔