参数嗅探vs变量vs重新编译vs优化未知


40

因此,我们有一个长时间运行的进程,导致今天早晨出现问题(30秒+运行时间)。我们决定检查是否应该归因于参数嗅探。因此,我们重写了proc并将输入的参数设置为变量,以消除参数嗅探。一种尝试/正确的方法。Bam,查询时间得到改善(不到1秒)。在查看查询计划时,在原始索引未使用的索引中发现了改进。

只是为了验证我们没有得到误报,我们对原始proc进行了dbcc freeproccache,然后重新运行以查看改进的结果是否相同。但是,令我们惊讶的是,原始过程仍然运行缓慢。我们再次使用WITH RECOMPILE进行了尝试,但仍然很慢(我们在对proc的调用以及在proc自身内部尝试了重新编译)。我们甚至重新启动了服务器(显然是dev框)。

所以,我的问题是...当我们在空的计划缓存上收到相同的慢查询时,怎么应该怪参数嗅探呢?snif应该没有任何参数?

我们是否会受到与计划缓存无关的表统计信息的影响?如果是这样,为什么将传入参数设置为变量会有所帮助呢?

在进一步的测试中,我们还发现在proc DID的内部插入OPTION(优化未知)会获得预期的改进计划。

因此,你们中有些人比我聪明,您能否提供一些线索,以了解产生这种类型结果的幕后情况?

另一个要注意的是,慢速计划也有理由提前中止,GoodEnoughPlanFound而快速计划在实际计划中没有早期中止原因。

综上所述

  • 从传入参数中创建变量(1秒)
  • 重新编译(30秒以上)
  • dbcc freeproccache(30+秒)
  • 选项(为不知道而优化)(1秒)

更新:

请在此处查看慢速执行计划:https//www.dropbox.com/s/cmx2lrsea8q8mr6/plan_slow.xml

在此处查看快速执行计划:https : //www.dropbox.com/s/b28x6a01w7dxsed/plan_fast.xml

注意:出于安全原因,表,架构,对象名称已更改。

Answers:


43

查询是

SELECT SUM(Amount) AS SummaryTotal
FROM   PDetail WITH(NOLOCK)
WHERE  ClientID = @merchid
       AND PostedDate BETWEEN @datebegin AND @dateend 

该表包含103,129,000行。

快速计划通过ClientId在日期上使用残差谓词进行查找,但需要进行96次查找来检索Amount<ParameterList>计划中的部分如下。

        <ParameterList>
          <ColumnReference Column="@dateend" 
                           ParameterRuntimeValue="'2013-02-01 23:59:00.000'" />
          <ColumnReference Column="@datebegin" 
                           ParameterRuntimeValue="'2013-01-01 00:00:00.000'" />
          <ColumnReference Column="@merchid" 
                           ParameterRuntimeValue="(78155)" />
        </ParameterList>

慢速计划按日期查找,并具有查找以评估ClientId上的剩余谓词并检索金额的功能(估计1与实际7,388,383)。该<ParameterList>部分是

        <ParameterList>
          <ColumnReference Column="@EndDate" 
                           ParameterCompiledValue="'2013-02-01 23:59:00.000'" 
                           ParameterRuntimeValue="'2013-02-01 23:59:00.000'" />
          <ColumnReference Column="@BeginDate" 
                           ParameterCompiledValue="'2013-01-01 00:00:00.000'"               
                           ParameterRuntimeValue="'2013-01-01 00:00:00.000'" />
          <ColumnReference Column="@ClientID" 
                           ParameterCompiledValue="(78155)" 
                           ParameterRuntimeValue="(78155)" />
        </ParameterList>

在第二种情况下,ParameterCompiledValue不为空。SQL Server成功嗅探查询中使用的值。

《 SQL Server 2005实用故障排除》这本书说的是关于使用局部变量的信息

使用局部变量来消除参数嗅探是一种很常见的技巧,但是OPTION (RECOMPILE)OPTION (OPTIMIZE FOR)提示...通常是更优雅,风险更低的解决方案


注意

在SQL Server 2005中,语句级编译允许将存储过程中的单个语句的编译推迟到第一次执行查询之前进行。届时将知道局部变量的值。从理论上讲,SQL Server可以利用此功能来嗅探局部变量值,就像嗅探参数一样。但是,由于在SQL Server 7.0和SQL Server 2000+中通常使用局部变量来消除参数嗅探,因此在SQL Server 2005中未启用对局部变量的嗅探。在以后的SQL Server版本中可以启用局部变量嗅探,尽管这样做很好。如果可以选择,请使用本章概述的其他选项之一。


经过快速测试,上述行为在2008年和2012年仍然相同,并且仅在使用显式OPTION RECOMPILE提示时,才嗅探变量以进行延迟编译。

DECLARE @N INT = 0

CREATE TABLE #T ( I INT );

/*Reference to #T means this statement is subject to deferred compile*/
SELECT *
FROM   master..spt_values
WHERE  number = @N
       AND EXISTS(SELECT COUNT(*) FROM #T)

SELECT *
FROM   master..spt_values
WHERE  number = @N
OPTION (RECOMPILE)

DROP TABLE #T 

尽管推迟了编译,但该变量未被嗅探,并且估计的行数不准确

估算与实际

因此,我认为慢速计划与查询的参数化版本有关。

ParameterCompiledValue是等于ParameterRuntimeValue对所有的参数,所以这是不典型参数嗅探(其中计划被编译为一个组值然后另一组值运行)。

问题在于为正确的参数值编译的计划是不合适的。

您可能会按此处此处所述的日期递增来解决问题。对于具有1亿行的表,您需要在SQL Server自动为您更新统计信息之前插入(或修改)2000万。似乎上次更新它们时,零行与查询中的日期范围匹配,但现在有700万行。

您可以安排更频繁的统计信息更新,考虑2389 - 90使用跟踪标志或使用OPTIMIZE FOR UKNOWN它,以便仅依靠猜测而不是能够使用该datetime列上当前具有误导性的统计信息。

在下一版本的SQL Server(2012年之后)中,可能不需要这样做。一个相关的“连接”项包含有趣的响应

由Microsoft发布于2012年8月28日,下午1:35。
我们已经完成了下一个主要版本的基数估计增强,从本质上解决了这一问题。预览发布后,请继续关注细节。埃里克

本杰明·内瓦雷斯(Benjamin Nevarez)在文章末尾着眼于2014年的改进:

初探新的SQL Server基数估计器

在这种情况下,新的基数估计值似乎会回落并使用平均密度,而不是给出1行估计值。

有关2014基数估计器和此处的升序关键问题的其他一些详细信息:

SQL Server 2014中的新功能–第2部分–新基数估计


29

所以,我的问题是...当我们在空的计划缓存上收到相同的慢查询时,应该怎么怪参数嗅探...应该不应该嗅探任何参数?

当SQL Server编译包含参数值的查询时,它会嗅探这些参数的特定值以进行基数(行数)估计。在你的情况,对具体的数值@BeginDate@EndDate@ClientID选择一个执行计划时使用。您可以在此处此处找到有关参数嗅探的更多详细信息。我提供这些背景链接是因为上面的问题使我认为目前尚不完全了解该概念-编制计划时总会嗅探参数值。

无论如何,这都是关键,因为正如马丁·史密斯(Martin Smith)所指出的那样,参数嗅探并不是问题所在。在编译慢速查询时,统计信息表明@BeginDate和的嗅探值没有行@EndDate

慢计划嗅探值

嗅觉的价值是最近才出现的,这表明马丁提到了上升的关键问题。由于估计日期的索引查找仅返回单行,因此优化程序选择了一个计划,将谓词ClientID作为残差推到Key Lookup运算符上。

单行估计也是优化器停止寻找更好计划的原因,并返回“找到足够好的计划”消息。单行估算的慢速计划的估算总成本仅为0.013136成本单位,因此毫无意义地寻找更好的方案。当然,除了查找实际上返回了7,388,383行,而不是一行,从而导致了相同数量的键查找。

统计信息可能很难保持最新,并且在大型表上很有用,分区在这方面带来了自身的挑战。我自己在跟踪标记2389和2390方面还没有取得特别的成功,但是欢迎您对其进行测试。SQL Server的较新版本(R2 SP1和更高版本)具有可用的动态统计信息更新,但是仍未实现此按分区的统计信息更新。同时,您可能希望在对该表进行重大更改时安排手动统计信息更新。

对于这个特定的查询,我会考虑在编译快速查询计划期间实现优化程序建议的索引:

/*
The Query Processor estimates that implementing the following index could improve
the query cost by 98.8091%.

WARNING: This is only an estimate, and the Query Processor is making this 
recommendation based solely upon analysis of this specific query.
It has not considered the resulting index size, or its workload-wide impact,
including its impact on INSERT, UPDATE, DELETE performance.
These factors should be taken into account before creating this index.
*/
CREATE NONCLUSTERED INDEX [<Name of Missing Index>]
ON [dbo].[PDetail] ([ClientID],[PostedDate])
INCLUDE ([Amount]);

该索引应与ON PartitionSchemeName (PostedDate)子句对齐,与分区对齐,但要点是,提供明显最佳的数据访问路径将有助于优化程序避免选择较差的计划,而无需求助于OPTIMIZE FOR UNKNOWN提示或老式的变通方法,例如使用局部变量。

使用改进的索引,Amount将消除用于检索列的键查找,查询处理器仍可以执行动态分区消除,并使用查找来查找特定的ClientID日期范围。


希望我可以将两个答案标记为正确,但是再次感谢您提供的其他信息-非常有启发性。
RThomas

1
自从我发布此消息已经有几年了...但是我只是想让您知道。我一直都用“不完全理解”这个词,在我这样做的时候我总是想到保罗·怀特。让我每次都笑。
RThomas 2015年

0

我有一个地方存储过程变得慢了完全相同的问题,并OPTIMIZE FOR UNKNOWNRECOMPILE查询提示解决缓慢和加快了执行时间。但是,以下两种方法不会影响存储过程的速度:(i)清除缓存(ii)使用WITH RECOMPILE。因此,就像您说的那样,这实际上不是参数嗅探。

跟踪标志2389和2390也没有帮助。只需更新统计信息(EXEC sp_updatestats)即可为我完成。

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.