T-SQL查询使用完全不同的计划,具体取决于我要更新的行数


20

我有一个带有“ TOP(X)”子句的SQL UPDATE语句,而我要更新值的行大约有40亿行。当我使用“ TOP(10)”时,我得到一个几乎立即执行的执行计划,但是当我使用“ TOP(50)”或更大时,查询永远不会完成(至少在我等待时)。它使用完全不同的执行计划。较小的查询使用一个非常简单的计划,该计划带有一对索引查找和一个嵌套循环联接,其中完全相同的查询(UPDATE语句的TOP子句中的行数不同)使用的方案涉及两个不同的索引查找,表假脱机,并行性以及其他一系列复杂性。

我已经使用“ OPTION(USE PLAN ...)”来强制它使用由较小查询生成的执行计划-当我这样做时,我可以在几秒钟内更新多达100,000行。我知道查询计划是好的,但是SQL Server仅在只涉及少量行的情况下才会自行选择该计划-更新中任何相当大的行数都会导致次优计划。

我以为应该归咎于并行性,所以我MAXDOP 1对查询进行设置,但是没有任何效果-这一步骤已经过去,但是糟糕的选择/性能却没有。我也sp_updatestats今天早上跑了,以确保不是这个原因。

我已经附上了两个执行计划-计划越短也越快。此外,这是有问题的查询(值得注意的是,无论行数是大还是小,我包含的SELECT似乎都是快速的):

    update top (10000) FactSubscriberUsage3
               set AccountID = sma.CustomerID
    --select top 50 f.AccountID, sma.CustomerID
      from FactSubscriberUsage3 f
      join dimTime t
        on f.TimeID = t.TimeID
      join #mac sma
        on f.macid = sma.macid
       and t.TimeValue between sma.StartDate and sma.enddate 
     where f.AccountID = 0 --There's a filtered index on the table for this

这是快速的计划快速执行计划

这是较慢的一个慢执行计划

我设置查询的方式或执行计划中是否有任何明显的内容,这会导致查询引擎做出错误的选择?如有必要,我还可以包括所涉及的表定义以及在其上定义的索引。

对于那些要求仅提供统计信息的数据库对象版本的人: 我什至没有意识到您可以做到这一点,但这完全有道理!我试图为仅统计数据的数据库生成脚本,以便其他人可以自己测试执行计划,但是我可以在过滤后的索引上生成统计数据/直方图(似乎脚本中的语法错误),所以我那里运气不好。我尝试删除过滤器,但查询计划很接近,但并不完全相同,我也不想派人追赶。

更新和一些更完整的执行计划: 首先,SQL Sentry的Plan Explorer是一个了不起的工具。在查看本网站上的其他查询计划问题之前,我什至不知道它的存在,而且关于我的查询如何执行还有很多话要说。尽管我不确定如何解决该问题,但他们明确指出了问题所在。

这是10、100和1000行的摘要-您可以看到1000行查询与其他查询完全不同: 声明摘要

您可以看到第三个查询的读取次数非常可笑,因此显然它在做完全不同的事情。这是带有行数的估计执行计划。 1000行估计执行计划: 1000行的估计执行计划

这是执行计划的实际结果(顺便说一句,“永不完成”,事实证明我的意思是“在一小时内完成”)。 1000行实际执行计划 1000行实际执行计划

我注意到的第一件事是,而不是从dimTime表拉60K行一样,它预计,它的实际拉动1.6十亿,用B。查看我的查询,我不确定它是如何从dimTime表中撤回这么多行的。我使用的BETWEEN运算符只是确保我根据事实表中的时间记录从#mac中提取正确的记录。但是,当我在WHERE子句中添加一行以将t.TimeValue(或t.TimeID)过滤为单个值时,我可以在几秒钟内成功更新100,000行。结果,如我所包含的执行计划中所述,很明显这是我的时间表是问题所在,但是我不确定如何更改联接条件来解决此问题并保持准确性。有什么想法吗?

供参考,此处为100行更新的计划(带行计数)。您可以看到它命中相同的索引,并且仍然有大量的行,但是问题的严重程度相差无几。 具有行数的100行执行在此处输入图片说明


这是GOTTA Be统计数据。你sp_updatestatistics在桌子上跑了吗?
JNK 2012年

@JNK:我最初是这么认为的,但是已经运行了sp_updatestats且没有任何更改。我只是再次运行它,它并不关心更新查询所涉及的任何索引的统计信息。不过谢谢!
SqlRyan 2012年

第二个是一个宽泛的(每个索引)更新计划,而不是一个狭窄的(每行)更新计划,它解释了一些额外的可见复杂性。但实际上唯一的区别是加入顺序from #mac sma join f on f.macid = sma.macid join dimTime t on f.TimeID = t.TimeID and t.TimeValue between sma.StartDate and sma.enddatefrom #mac join t on t.TimeValue between sma.StartDate and sma.enddate join f on f.TimeID = t.TimeID and f.macid = sma.macid
Martin Smith

这有些不对劲。即使是昂贵的查询计划,也应该逐步生成行。A TOP 50仍应快速执行。您可以上传XML计划吗?我需要查看行数。您可以TOP 50使用maxdop 1并作为选择而不是作为更新来运行计划吗?(试图简化/平分搜索空间)。
usr

@usr联接t.TimeValue between sma.StartDate and sma.enddate可能最终会生成更多无用的行,这些行随后会在针对FactSubscriber的联接中被过滤掉,因此不要最终得到最终结果。
马丁·史密斯

Answers:


3

dimTime的索引正在更改。更快的计划是使用_dta索引。首先,请确保在sys.indexes中未将其标记为假设索引。

认为您可能会通过使用#mac表进行过滤而不是仅在@StartDate和@enddate之间提供WHERE t.TimeValue这样的开始/结束日期来绕过一些参数化。摆脱该临时表。


dta前缀索引看起来就像是通过遵循DTA建议而创建的,而无需自定义名称。假设索引不能出现在实际的执行计划中(并且如果没有一些未记录的命令也不会被估计)。不知道您的第二个建议将如何工作。t.TimeValue between sma.StartDate and sma.enddate是相关的,因此可以针对#temp表中的每一行进行更改。OP将其替换为什么?
Martin Smith

公平地说,我对临时表没有给予足够的重视。
william_a_dba 2012年

1
但是,假设索引确实可以搞乱执行计划。如果是假想的,则应将其删除并重新创建。 blogs.technet.com/b/anurag_sharma/archive/2008/04/15/...
william_a_dba

当DTA在完成前未完成/冻结时,将保留假设索引。如果DTA出现故障,则必须手动清理它们。
william_a_dba 2012年

1
@william_a_dba-啊,我明白了你的意思(在阅读了链接之后)。该查询永远不会完成,因为它会不断地重新编译。有趣!
马丁·史密斯

1

在计划中没有更多关于行数的信息时,我的初步建议是在查询中安排正确的联接顺序,并使用强制它OPTION (FORCE ORDER)。强制执行第一个计划的加入顺序。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.