为啥我的两台服务器之间的实际执行计划不同?
Posted
技术标签:
【中文标题】为啥我的两台服务器之间的实际执行计划不同?【英文标题】:Why am I getting a different actual execution plan between my two servers?为什么我的两台服务器之间的实际执行计划不同? 【发布时间】:2019-05-06 06:17:50 【问题描述】:我有一个在生产和开发环境中运行的 SQL Server 查询。完全相同的查询。
SELECT DISTINCT
[Record_Transformation_ACCRUALS],
[Record_Transformation_FA:AMORTIZATION],
[Record_Transformation_BONUS:AMORTIZATION],
[Record_Transformation_CPH:BYLABOUR],
[Record_Transformation_CPH:BYTARGETHOURS],
[Record_Transformation_OVERHEAD:CULTURE],
[Record_Transformation_DEDICATED COSTCENTER],
[Record_Transformation_PUSHDOWN:EXPENSE],
[Record_Transformation_OVERHEAD:FACILITIES],
[Record_Transformation_OVERHEAD:GENOME],
[Record_Transformation_TAXES:MANAGEMENT],
[Record_Transformation_TAXES:MARKETING],
[Record_Transformation_OVERHEAD:OFFICETECH],
[Record_Transformation_EXPENSE:PASSTHROUGH],
[Record_Transformation_OVERHEAD:PEOPLEPRACTICES],
[Record_Transformation_OVERHEAD:RECRUITING],
[Record_Transformation_TAXES:SALES],
[Record_Transformation_Static Transfer],
[Record_Label]
FROM
Warehouse_20181204
WHERE
Is_Target_Employee = 1 OR Is_Source_Employee = 1
我们比较了这两个表的创建脚本,它们是相同的(除了相关表的名称)。
我们还验证了它们都在使用聚集列存储索引。
在开发过程中,此查询只需不到一秒钟的时间。在 prod 上大约需要一分钟。起初我们认为数据的大小可能是问题所在,但差异很小(几十万行)。
然后我们检查了两者的实际执行计划。在 dev 上,实际的执行计划是:
在 prod 上,实际的执行计划却大不相同:
我们发现自己很难理解为什么会这样。我们已验证 SQL Server 的版本是相同的:
Microsoft SQL Server 2017 (RTM-CU5) (KB4092643) - 14.0.3023.8 (X64)
Web Edition (64-bit) on Windows Server 2016 Datacenter 10.0 <X64> (Build 14393:) (Hypervisor)
我的问题有两个:
-
我们如何确定为什么 prod 和 dev 之间的执行计划如此不同?
在给定类似数据集的情况下,如何让 prod 环境与 dev 环境一样快地运行?
编辑:
一些额外要求的细节:
两台服务器都有 8G 内存,运行时都有超过 1G 的空闲空间 两台服务器都有 2 个处理器 硬件与您得到的一样 - 两个相同大小的 aws 实例 我们已验证两个表和聚集列存储索引的 sql 是相同的希望这些对当前 SQL 计划的所有其他细节有所帮助:
DEV sql 计划:https://gist.github.com/klick-barakgall/17a7ce926777a3257f7eecb32859458e
PROD sql 计划:https://gist.github.com/klick-barakgall/76eabf1008f5bfb0c51259c2ba3f509d
为那些有兴趣深入研究执行的人添加粘贴计划的链接。
DEV
PROD
【问题讨论】:
执行计划优化也是两面派;艺术和科学。 2个环境之间有很多相关的变量。您只能在 dev 上获得部分数据; # 使用该表的用户数;这些表的索引;记忆泡泡;关于 prod 中数据如何分区的文件组。 第一个计划是并行的,第二个不是。您的服务器是否具有相同的硬件,例如相同数量的处理器? 两个计划中从过滤器中出来的 估计 行是什么? (以及两者的最终结果?) 并行成本阈值的设置是否相同?服务器上的最大并行度是多少?两个盒子的统计数据都是最新的吗? 为 Jeroen 的问题添加了详细信息。 Martin_Smith 您要求的详细信息在我现在链接的 SQL 计划中。 @Zane:刚刚在两台服务器上运行了“UPDATE STATISTICS [table]”,然后在两台服务器上都运行了带有 OPTION(重新编译)的查询。两种性能都没有变化。两台服务器上的阈值并行成本相同 (5)。两台服务器上的最大度数也为零 (0).. 【参考方案1】:DISTINCT 使您的查询简写为:
SELECT
[Record_Transformation_ACCRUALS],
[Record_Transformation_FA:AMORTIZATION],
[Record_Transformation_BONUS:AMORTIZATION],
[Record_Transformation_CPH:BYLABOUR],
[Record_Transformation_CPH:BYTARGETHOURS],
[Record_Transformation_OVERHEAD:CULTURE],
[Record_Transformation_DEDICATED COSTCENTER],
[Record_Transformation_PUSHDOWN:EXPENSE],
[Record_Transformation_OVERHEAD:FACILITIES],
[Record_Transformation_OVERHEAD:GENOME],
[Record_Transformation_TAXES:MANAGEMENT],
[Record_Transformation_TAXES:MARKETING],
[Record_Transformation_OVERHEAD:OFFICETECH],
[Record_Transformation_EXPENSE:PASSTHROUGH],
[Record_Transformation_OVERHEAD:PEOPLEPRACTICES],
[Record_Transformation_OVERHEAD:RECRUITING],
[Record_Transformation_TAXES:SALES],
[Record_Transformation_Static Transfer],
[Record_Label]
FROM Warehouse_20181204
WHERE Is_Target_Employee = 1 OR Is_Source_Employee = 1
GROUP BY
[Record_Transformation_ACCRUALS],
[Record_Transformation_FA:AMORTIZATION],
[Record_Transformation_BONUS:AMORTIZATION],
[Record_Transformation_CPH:BYLABOUR],
[Record_Transformation_CPH:BYTARGETHOURS],
[Record_Transformation_OVERHEAD:CULTURE],
[Record_Transformation_DEDICATED COSTCENTER],
[Record_Transformation_PUSHDOWN:EXPENSE],
[Record_Transformation_OVERHEAD:FACILITIES],
[Record_Transformation_OVERHEAD:GENOME],
[Record_Transformation_TAXES:MANAGEMENT],
[Record_Transformation_TAXES:MARKETING],
[Record_Transformation_OVERHEAD:OFFICETECH],
[Record_Transformation_EXPENSE:PASSTHROUGH],
[Record_Transformation_OVERHEAD:PEOPLEPRACTICES],
[Record_Transformation_OVERHEAD:RECRUITING],
[Record_Transformation_TAXES:SALES],
[Record_Transformation_Static Transfer],
[Record_Label]
优化器尝试满足此类查询的两种最常见的方式。首先,它为Is_Target_Employee = 1 OR Is_Source_Employee = 1
过滤列存储索引;这就是您计划中的过滤器所显示的内容。接下来,要处理 GROUP BY(或 DISTINCT),它将:
-
对行进行排序,然后使用
Stream Aggregator
返回一个不同的集合(如 prod 执行计划中所示)
或
使用哈希匹配过滤行并返回不同的集合(如开发版所示)
我怀疑优化器选择了不同的计划,因为基数估计有很大不同。显然,开发计划表现得更好。它可能会表现得更好,因为在这种情况下哈希匹配计划更有效,它更有可能在 Dev 中表现得更好,因为您在 Dev 中获得了并行执行计划,在 Prod 中获得了串行计划。
我建议的行动方案是: 使用查询提示在 Prod 中运行您的查询 -
OPTION (QUERYTRACEON 8649);
这将强制优化器运行并行计划。如果您没有获得并行计划,那么您将遇到不同的问题(可能 Prod 中的 MAXDOP 设置设置为 1)。如果您确实获得了并行计划,并且它提高了性能,那么您已经确定了问题(您需要并行计划)。如果并行计划不能解决问题,那么您可能需要考虑在该表上使用非聚集的过滤列存储索引,该索引包括查询中的所有列,然后使用以下内容进行过滤:
WHERE Is_Target_Employee = 1 OR Is_Source_Employee = 1
您现在正在执行大扫描,读取大量您不需要阅读的行。
如有任何问题,请回复。
2018 年 12 月 6 日更新: 很抱歉更新晚了,很多工作都出现了。
我进一步研究了执行计划,发现了一些有趣的事情。在阅读@Martin_Smith 发布的内容之前,我截取了这些屏幕截图:
我 100% 同意串行与并行执行计划不是这里的问题,但就您的产品计划而言,串行运行会使糟糕的执行计划变得更慢。正如 Martin 解释的那样,问题在于哈希匹配计划是一个更好的计划。
两个计划都从列存储索引中检索相似数量的行(Dev 中为 5M,Prod 中为 6M)。在每个计划中,所有行都被过滤,但在 Prod 计划中全部 em> 行由排序运算符处理再次,而开发计划中的 338 行具有哈希聚合。
无论您投入多少 CPU:针对 630 万行的 19 列排序都会很慢,尤其是串行计划。并行性 IMO 的最佳用途之一是处理诸如此类的大型排序。也就是说,不应该需要排序。我以前见过这种情况,当优化器可以使用排序(如在您的产品计划中)或使用哈希(如在您的开发计划中)解决查询时 - 当您强制并行执行时,它将选择带有哈希的计划计划。我怀疑在您的情况下强制执行并行计划会导致优化器选择使用哈希的计划。
最后 - 我之前忘了提这个,不要在 Prod 中使用 OPTION (QUERYTRACEON 8649)
;它是无证的。我用它来测试。在产品中使用make_parallel by Adam Machanic
【讨论】:
感谢您的回复@Alan_Burstein。不幸的是,直到明天我才靠近可以访问 prod 环境的机器来尝试这个,我肯定会在早上。当我可以保证数据集几乎相同且方案相同时,您对为什么cardinality estimates
会如此不同有任何想法吗?
好的,我需要一些时间来看看这个。首先,要阅读您发布的执行计划,我需要下载然后将它们重命名为具有 .sqlplan 扩展名。这使得在 SSMS 中打开它们并查看计划成为可能。我现在正在查看计划,很快就会更新我的答案。
所以我试了一下您的查询,它确实可以立即运行!我们认为这可能是一个很好的故障排除步骤,但并不能完全解决我们的问题,因为我们希望确保我们生成的通用查询 A)快速运行,而且 B)在 dev 和 prod 之间运行类似。这是否说明了它为何如此不同?
查看我上面更新的帖子。要回答您关于试图让查询在 Prod 与 Dev 中以类似方式运行的查询的问题 - 这是一个复杂的问题......显然,让一切尽可能相同或相似(服务器配置、硬件等)您可以查看计划指南(我认为他们可能会有所帮助,但我没有与他们合作太多)。我只能说测试,测试,测试。考虑使用查询存储和/或扩展事件——两者都在 SQL Server 2017 中可用。这两种工具都有助于分析这类事情。【参考方案2】:
串行与并行并不是真正的问题,因为无论如何您的最大并行度只有 2。
导致您的慢查询的原因是内存授予不足和大量排序溢出(到级别 8)
您的查询返回 305
行,但 SQL Server 在一个计划中估计 2,561,980
而在另一个计划中估计 3,709,060
。
对于 305 行,您需要一个哈希聚合,因为它只需要存储 305 个不同的分组值的内存,而不需要存储整个 600 万行的内存以及排序使用的额外开销。
即使在具有哈希聚合的计划中,输出行数的高估也意味着您会收到过多的内存授予警告。
查询内存授权检测到“ExcessiveGrant”,这可能会影响 可靠性。授权大小:初始 831,800 KB,最终 831,800 KB,已使用 20,480 KB。
为此,您可以尝试以下方法来创建多列统计信息,以便准确估计组数,以便 SQL Server 自然选择具有适当大小的内存授予的哈希组。 FULLSCAN
可能不需要,但是当我设置测试时,默认采样似乎不足以让优化器使用新统计数据中的密度信息和新基数估计器。
CREATE STATISTICS SomeName ON Warehouse_20181204 (
[Record_Transformation_ACCRUALS],
[Record_Transformation_FA:AMORTIZATION],
[Record_Transformation_BONUS:AMORTIZATION],
[Record_Transformation_CPH:BYLABOUR],
[Record_Transformation_CPH:BYTARGETHOURS],
[Record_Transformation_OVERHEAD:CULTURE],
[Record_Transformation_DEDICATED COSTCENTER],
[Record_Transformation_PUSHDOWN:EXPENSE],
[Record_Transformation_OVERHEAD:FACILITIES],
[Record_Transformation_OVERHEAD:GENOME],
[Record_Transformation_TAXES:MANAGEMENT],
[Record_Transformation_TAXES:MARKETING],
[Record_Transformation_OVERHEAD:OFFICETECH],
[Record_Transformation_EXPENSE:PASSTHROUGH],
[Record_Transformation_OVERHEAD:PEOPLEPRACTICES],
[Record_Transformation_OVERHEAD:RECRUITING],
[Record_Transformation_TAXES:SALES],
[Record_Transformation_Static Transfer],
[Record_Label] ) WITH FULLSCAN
或者您可以考虑重组您的数据库,使 20 列左右的列与 305 行(加上数据中存在但被 WHERE 子句排除的任何其他组合)在一个单独的表中,并且您的原始表只有一个指向这个新表的 id。
然后您可以将其重写为小表中的 SELECT,其中 groupid IN (SELECT groupid FROM large table WHERE....)
【讨论】:
@Martin_Smith:您的解决方案完美运行。我很好奇,如果您对为什么 Prod 需要统计数据而不是 Dev 需要任何理论?有什么想法吗? @Martin_Smith:实际上我认为我们可能已经弄清楚了。我们刚刚使用新数据集更新了 dev(现在有超过 6M 行,prod 为 6.5M)。该数据集现在生成与 prod 相同的计划。我猜有一个阈值行数加上糟糕的统计数据,这给了我们一个糟糕的查询计划。这听起来对吗?以上是关于为啥我的两台服务器之间的实际执行计划不同?的主要内容,如果未能解决你的问题,请参考以下文章
使用 Visual Studio 2010 在 C# 中的两台不同机器上运行的客户端和服务器之间的通信
同一网段的两台服务器一台能PING通,一台PING不通,为啥呀?