为什么我的两台服务器之间有不同的实际执行计划?

时间:2018-12-04 18:37:14

标签: sql-server tsql sql-server-2017

我有一个同时在生产和开发环境上运行的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

我们已经比较了这两个表的创建脚本,它们是相同的(除了有问题的表的名称之外)。

我们还验证了它们都使用了群集的列存储索引。

在开发中,此查询花费不到一秒钟的时间。在产品上大约需要一分钟。我们最初以为数据的大小可能是个问题,但是差异很小(数十万行)。

然后,我们检查了两者的实际执行计划。在开发人员上,实际的执行计划是:

Dev execution plan

在产品上,实际的执行计划却大不相同:

Prod execution plan

我们发现自己为此感到困惑。我们已验证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)

我的问题有两个:

  1. 我们如何确定prod和dev之间的执行计划为何如此不同?
  2. 在给定相似数据集的情况下,如何才能使产品环境以与dev一样快的速度运行?

编辑:

一些其他要求的详细信息:

  • 两台服务器均具有8G内存,均可以免费运行1G以上
  • 两个服务器都有2个处理器
  • 硬件与您获得的相同-两个相同大小的AWS实例
  • 我们已经验证了两个表的SQL和群集列存储索引的SQL是否相同

希望这些对当前SQL计划的所有其他细节有所帮助:

DEV sql计划:https://gist.github.com/klick-barakgall/17a7ce926777a3257f7eecb32859458e

PROD sql计划:https://gist.github.com/klick-barakgall/76eabf1008f5bfb0c51259c2ba3f509d

为有兴趣挖掘执行的人员添加链接以粘贴计划。

DEV

PROD

2 个答案:

答案 0 :(得分:3)

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),它将:

  1. 对行进行排序,然后使用Stream Aggregator返回不同的集合(如产品执行计划中所示) OR
  2. 使用哈希匹配来过滤行并返回不同的集合(如开发版本所示)

我怀疑优化器选择的计划是不同的,因为基数估算值大不相同。显然,开发计划的执行情况更好。由于Hash匹配计划在这种情况下效率更高,因此它可能会表现更好, 它很有可能在Dev中表现更好,因为您在Dev中获得了并行执行计划,而在Prod中获得了串行计划。 / em>

我建议采取的行动方针是: 使用查询提示在Prod中运行查询-

OPTION (QUERYTRACEON 8649);

这将迫使优化器运行并行计划。如果没有并行计划,那么您会遇到其他问题(也许Prod中的MAXDOP设置被设置为1)。如果您确实获得了并行计划,并且可以提高性能,那么您已经确定了问题(需要并行计划)。如果并行计划不能解决问题,则您可能需要考虑对该表进行非聚集,筛选的列存储索引,该索引包括查询中的所有列,然后使用以下方法进行筛选:

WHERE Is_Target_Employee = 1 OR Is_Source_Employee = 1

您正在进行一次大扫描,无需读取大量的行。

如有任何疑问,请发回。

更新12/6/2018: 抱歉,更新晚了,出现了很多工作。

我进一步研究了执行计划,并发现了一些有趣的事情。在阅读@Martin_Smith发布的内容之前,我已经截取了这些屏幕截图:

enter image description here

enter image description here

我100%同意串行与并行执行计划不是这里的问题,但是就您的生产计划而言,串行运行会使可怕的执行计划变得更慢。正如Martin所解释的那样,问题在于哈希匹配计划是一个更好的计划。

两个计划均从columnstore索引中检索到相似数量的行(在Dev中为5M,在Prod中为6M。)在每个计划中,所有行均被过滤,但在Prod计划中, 全部 行由排序运算符再次进行 处理,而在Dev计划中的338行中使用了散列聚合。

无论您要投入多少CPU:针对630万行的19列排序都会变慢,尤其是采用串行计划。并行性IMO的最佳用途之一是处理诸如此类的大型事件。就是说,不需要排序。我之前曾见过这种情况,当优化程序可以使用某种排序(如您的Prod计划)或使用哈希(如您的Dev计划)来解决查询时-当您强制并行执行时,它将使用哈希来选择计划计划。在您的情况下,我怀疑强制并行计划会导致优化器选择使用哈希的计划。

最后-我以前忘记提及这一点,不要在Prod中使用OPTION (QUERYTRACEON 8649);它没有证件。我用它来测试。在生产中使用make_parallel by Adam Machanic

答案 1 :(得分:2)

串行vs并行并不是真正的问题,因为您的最大并行度始终只有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。

然后您可以将其重写为从小表中的组ID的SELECT(在大表WHERE ....中的SELECT groupid ...)