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等的概念。我很好奇的是如何更改查询以使其使用更好的计划。目前我正在将数据转储到临时表中,然后从中取出第一行。我想知道是否有更好的方法。
编辑对于事后阅读本文的人,这里有一些额外的信息。
当我开始时,没有其他索引。我在 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 运算符,其余的应该看起来就像你的好计划。
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)已指定一样)。从文档链接:
这可能不会在示例中产生任何不良影响,但总的来说,它很可能会产生。FORCE ORDER(暗示或明确)是一个非常强大的提示,超越了强制执行的顺序;它阻止了广泛的优化器技术的应用,包括部分聚合和重新排序。
一个OPTION (HASH JOIN) 查询提示可以是在合适的情况下较少干扰,因为这并不暗示FORCE ORDER。但是,它确实适用于查询中的所有连接。可以使用其他解决方案。
Rob*_*ley 29
编辑:+1 在这种情况下有效,因为事实证明这FILE_NUMBER是整数的零填充字符串版本。对于字符串,这里更好的解决方案是追加''(空字符串),因为追加值会影响顺序,或者为数字添加常量但包含非确定性函数的内容,例如sign(rand()+1). “打破排序”的想法在这里仍然有效,只是我的方法不理想。
+1
不,我并不是说我同意任何事情,我的意思是作为一种解决方案。如果您将查询更改为ORDER BY cj.FILE_NUMBER + 1,TOP 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,如果不是现在,那么在将来的某个时候。
我已经阅读了这篇文章的结果并了解了 Row Goal 等的概念。我很好奇的是如何更改查询以便它使用更好的计划
添加仅OPTION (QUERYTRACEON 4138)关闭该查询的行目标的影响,而不会对最终计划过于规范,并且可能是最简单/最直接的方法。
如果添加此提示会给您带来权限错误(对于 而言是必需的DBCC TRACEON),您可以使用计划指南来应用它:
使用QUERYTRACEON在计划指南的spaghettidba
...或者只是使用存储过程: