一尘不染

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

sql-server

我有一个带有“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 行,而是实际上提取了 16 亿行,带有 B。查看我的查询,我不确定它是如何从 dimTime 表中拉回那么多行的。我使用的 BETWEEN 运算符只是确保我根据 Fact 表中的时间记录从 #mac 中提取正确的记录。但是,当我在 WHERE 子句中添加一行并将 t.TimeValue(或 t.TimeID)过滤为单个值时,我可以在几秒钟内成功更新 100,000 行。因此,正如我在执行计划中明确指出的那样,很明显我的时间表是问题所在,但我不确定如何更改连接标准以解决此问题并保持准确性. 有什么想法吗?

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


阅读 102

收藏
2022-11-25

共1个答案

一尘不染

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

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

2022-11-25