根据我正在更新的行数,使用完全不同的计划的 T-SQL 查询

Sql*_*yan 20 performance sql-server execution-plan query-performance

我有一个带有“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
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

更新和一些更完整的执行计划: 首先,SQL Sentry 的计划资源管理器是一个令人难以置信的工具。在查看此站点上的其他查询计划问题之前,我什至不知道它的存在,并且它有很多关于我的查询执行方式的说明。虽然我不确定如何解决这个问题,但他们清楚地表明了问题所在。

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

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

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

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

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

小智 3

dimTime 上的索引正在变化。更快的计划是使用 _dta 索引。首先,确保它没有在 sys.indexes 中标记为假设索引。

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