RTh*_*mas 41 sql-server-2008 execution-plan cache
所以今天早上我们有一个长时间运行的 proc 导致问题(30 秒 + 运行时间)。我们决定检查参数嗅探是否是罪魁祸首。因此,我们重写了 proc 并将传入的参数设置为变量,以阻止参数嗅探。一种尝试过的/真实的方法。Bam,查询时间得到改善(不到 1 秒)。在查看查询计划时,在原始未使用的索引中发现了改进。
为了验证我们没有得到误报,我们对原始 proc 进行了 dbcc freeproccache 并重新运行以查看改进的结果是否相同。但是,令我们惊讶的是,原来的 proc 仍然运行缓慢。我们再次尝试使用 WITH RECOMPILE,但仍然很慢(我们尝试在对 proc 的调用以及在 proc 内部进行重新编译)。我们甚至重新启动了服务器(显然是开发箱)。
所以,我的问题是……当我们在空计划缓存上得到相同的慢查询时,参数嗅探怎么会受到指责……不应该有任何参数嗅探???
我们是否会受到与计划缓存无关的表统计信息的影响。如果是这样,为什么将传入参数设置为变量会有所帮助??
在进一步的测试中,我们还发现,将在PROC的内部的OPTION(OPTIMIZE未知)DID得到预期的改进计划。
所以,你们中的一些人比我更聪明,你能提供一些关于幕后发生了什么来产生这种结果的线索吗?
另一方面,慢计划也有理由提前中止,GoodEnoughPlanFound而快计划在实际计划中没有提前中止原因。
总之
更新:
在此处查看慢速执行计划:https : //www.dropbox.com/s/cmx2lrsea8q8mr6/plan_slow.xml
在此处查看快速执行计划:https : //www.dropbox.com/s/b28x6a01w7dxsed/plan_fast.xml
注意:出于安全原因,表、架构、对象名称已更改。
Mar*_*ith 43
查询是
SELECT SUM(Amount) AS SummaryTotal
FROM PDetail WITH(NOLOCK)
WHERE ClientID = @merchid
AND PostedDate BETWEEN @datebegin AND @dateend
Run Code Online (Sandbox Code Playgroud)
该表包含 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>
Run Code Online (Sandbox Code Playgroud)
慢速计划按日期查找并查找以评估 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>
Run Code Online (Sandbox Code Playgroud)
在第二种情况下的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
Run Code Online (Sandbox Code Playgroud)
尽管延迟编译,变量没有被嗅探并且估计的行数不准确

所以我假设慢速计划与查询的参数化版本有关。
的ParameterCompiledValue是等于ParameterRuntimeValue对所有的参数,所以这是不典型参数嗅探(其中计划被编译为一个组值然后另一组值运行)。
问题是为正确的参数值编译的计划是不合适的。
您可能会遇到此处和此处描述的升序日期问题。对于具有 1 亿行的表,您需要插入(或以其他方式修改)2000 万行,SQL Server 才会自动为您更新统计信息。似乎上次更新时零行与查询中的日期范围匹配,但现在有 700 万行。
您可以安排更频繁的统计更新,考虑跟踪标志2389 - 90或使用OPTIMIZE FOR UKNOWN它只是依靠猜测而不是能够使用datetime列上当前具有误导性的统计信息。
在 SQL Server 的下一版本(2012 年之后)中,这可能不是必需的。一个相关的连接项目包含了耐人寻味的回应
Microsoft 于 2012 年 8 月 28 日下午 1:35 发布
我们为下一个主要版本进行了基数估计增强,基本上解决了这个问题。一旦我们的预览出来,请继续关注详细信息。埃里克
本杰明·内瓦雷斯 (Benjamin Nevarez) 在文章末尾介绍了 2014 年的改进:
在这种情况下,新的基数估计器似乎会回退并使用平均密度,而不是给出 1 行估计值。
有关 2014 年基数估计器和升序关键问题的一些其他详细信息,请参见此处:
SQL Server 2014 中的新功能 – 第 2 部分 – 新基数估计
Pau*_*ite 29
所以,我的问题是……当我们在空的计划缓存上得到相同的慢查询时,参数嗅探怎么会受到指责……不应该有任何参数嗅探?
当 SQL Server 编译包含参数值的查询时,它会嗅探这些参数的特定值以进行基数(行计数)估计。在您的情况下,在选择执行计划时使用@BeginDate,@EndDate和的特定值@ClientID。您可以在此处和此处找到有关参数嗅探的更多详细信息。我提供这些背景链接是因为上面的问题让我认为这个概念目前还没有完全理解——编译计划时总是有参数值需要嗅探。
无论如何,这都是无关紧要的,因为参数嗅探不是这里的问题,正如 Martin Smith 指出的那样。在编译慢查询时,统计信息表明没有用于嗅探@BeginDate和的值的行@EndDate:

嗅探到的值是最近的,表明Martin 提到的上升的关键问题。由于估计日期上的索引查找仅返回单行,因此优化器会选择一个计划,将谓词ClientID作为残差推送到 Key Lookup 运算符。
单行估计也是优化器停止寻找更好计划的原因,返回一个 Good Enough Plan Found 消息。使用单行估算的慢速计划的估算总成本仅为 0.013136 个成本单位,因此尝试寻找更好的方案毫无意义。当然,除了查找实际上返回 7,388,383 行而不是 1 行,导致相同数量的键查找。
在大表上保持最新和有用的统计数据可能很棘手,而分区在这方面引入了它自己的挑战。我自己在跟踪标志 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]);
Run Code Online (Sandbox Code Playgroud)
索引应该是分区对齐的,有一个ON PartitionSchemeName (PostedDate) 子句,但关键是提供一个明显最佳的数据访问路径将帮助优化器避免糟糕的计划选择,而无需求助于OPTIMIZE FOR UNKNOWN提示或使用局部变量等老式解决方法。
使用改进的索引,检索Amount列的Key Lookup将被消除,查询处理器仍然可以执行动态分区消除,并使用seek 查找特定ClientID和日期范围。
| 归档时间: |
|
| 查看次数: |
17839 次 |
| 最近记录: |