OPTION(RECOMPILE)总是更快;为什么?

时间:2014-01-01 01:59:29

标签: sql sql-server sql-server-2008 compilation hint

我遇到了一个奇怪的情况,我的查询附加OPTION (RECOMPILE)会导致它在半秒内运行,而省略它会导致查询花费超过五分钟。

通过SqlCommand.ExecuteReader()从查询分析器或我的C#程序执行查询时就是这种情况。拨打(或不拨打)DBCC FREEPROCCACHEDBCC dropcleanbuffers没有任何区别;查询结果总是在OPTION (RECOMPILE)时立即返回,如果没有它则超过五分钟。始终使用相同的参数调用查询[为了此测试]。

我正在使用SQL Server 2008。

我对编写SQL非常熟悉,但之前从未在查询中使用OPTION命令,并且在扫描此论坛上的帖子之前不熟悉计划缓存的整个概念。我对帖子的理解是OPTION (RECOMPILE)是一项昂贵的操作。它显然为查询创建了一个新的查找策略。那么为什么那么省略OPTION (RECOMPILE)的后续查询是如此之慢?后续查询是否应该使用在前一次调用中计算的查找策略,其中包括重新编译提示?

在每次调用时都需要重新编译提示的查询是否非常罕见?

对于入门级问题感到抱歉,但我无法真正做到这一点。

更新:我被要求发布查询...

select acctNo,min(date) earliestDate 
from( 
    select acctNo,tradeDate as date 
    from datafeed_trans 
    where feedid=@feedID and feedDate=@feedDate 

    union 

    select acctNo,feedDate as date 
    from datafeed_money 
    where feedid=@feedID and feedDate=@feedDate 

    union 

    select acctNo,feedDate as date 
    from datafeed_jnl 
    where feedid=@feedID and feedDate=@feedDate 
)t1 
group by t1.acctNo
OPTION(RECOMPILE)

从查询分析器运行测试时,我会添加以下行:

declare @feedID int
select @feedID=20

declare @feedDate datetime
select @feedDate='1/2/2009'

从我的C#程序调用它时,参数通过SqlCommand.Parameters属性传入。

出于本讨论的目的,您可以假设参数永远不会改变,因此我们可以排除次优参数嗅觉作为原因。

5 个答案:

答案 0 :(得分:136)

有时使用OPTION(RECOMPILE)是有道理的。根据我的经验,这是一个可行的选项,唯一一次是使用动态SQL。在您探究在您的情况下这是否有意义之前,我建议您重建统计数据。这可以通过运行以下命令来完成:

EXEC sp_updatestats

然后重新创建执行计划。这将确保在创建执行计划时,它将使用最新信息。

每次执行查询时,添加OPTION(RECOMPILE)都会重建执行计划。我从未听说过被描述为creates a new lookup strategy,但也许我们只是对同一件事使用不同的术语。

创建存储过程时(我怀疑你是从.NET调用ad-hoc sql而if you are using a parameterized query then this ends up being a stored proc call)SQL Server会尝试根据数据库中的数据确定此查询的最有效执行计划,传入的参数(parameter sniffing),然后缓存此计划。这意味着如果您创建查询,其中数据库中有10条记录,然后在有100,000,000条记录时执行它,则缓存的执行计划可能不再是最有效的。

总结 - 我认为OPTION(RECOMPILE)在这里没有任何理由。我怀疑你只需要更新统计数据和执行计划。根据您的具体情况,重建统计数据可能是DBA工作的重要组成部分。如果您在更新统计数据后仍然遇到问题,我建议发布两个执行计划。

回答你的问题 - 是的,我会说你每次执行查询时都要重新编译执行计划是非常不寻常的。

答案 1 :(得分:124)

通常,当查询的运行与运行之间存在巨大差异时,我发现它通常是5个问题中的一个。

  1. ç计 - 统计数据已过期。数据库存储有关表和索引的各列中值类型的范围和分布的统计信息。这有助于查询引擎开发一个"计划"攻击如何进行查询,例如它将使用散列或查看整个集合来匹配表之间的键的方法类型。您可以在整个数据库或仅某些表或索引上调用Update Statistics。这会将查询从一次运行减慢到另一次运行,因为当统计信息过期时,查询计划可能不适合同一查询的新插入或更改的数据(稍后将详细说明)。在生产数据库上立即更新统计信息可能不合适,因为会有一些开销,减速和滞后,具体取决于要采样的数据量。您还可以选择使用“完全扫描”或“采样”来更新统计信息。如果查看查询计划,您还可以使用命令 DBCC SHOW_STATISTICS(tablename,indexname)查看正在使用的索引的统计信息。这将显示查询计划用于基于其方法的键的分布和范围。

  2. PARAMETER SNIFFING - 即使查询本身未更改,缓存的查询计划也不是您传入的特定参数的最佳选择。例如,如果传入的参数仅检索1,000,000行中的10个,则创建的查询计划可能使用散列连接,但是如果传入的参数将使用1,000,000行中的750,000行,则创建的计划可能是索引扫描或表扫描。在这种情况下,您可以告诉SQL语句使用选项 OPTION(RECOMPILE)或SP来使用WITH RECOMPILE。告诉引擎这是一个"单一使用计划"而不是使用可能不适用的缓存计划。关于如何做出这个决定没有规则,这取决于知道用户使用查询的方式。

  3. INDEXES - 查询可能无法更改,但其他地方的更改(例如删除非常有用的索引)会降低查询速度。

  4. ROWS CHANGED - 您要查询的行会因呼叫而发生剧烈变化。通常在这些情况下会自动更新统计信息。但是,如果要在紧密循环中构建动态SQL或调用SQL,则可能会使用基于错误的行数或统计信息的过时查询计划。同样在这种情况下, OPTION(RECOMPILE)非常有用。

  5. THE LOGIC 它的逻辑,你的查询不再有效,它适用于少量行,但不再扩展。这通常涉及对查询计划进行更深入的分析。例如,您不能再批量处理,但必须使用Chunk并执行较小的Commits,或者您的Cross Product适用于较小的集合,但现在占用CPU和内存,因为它扩展得更大,这也可能适用于使用DISTINCT,你为每一行调用一个函数,你的密钥匹配因为CASTING类型转换或NULLS或函数而不使用索引......这里有太多可能性。

  6. 通常,当您编写查询时,您应该大致了解某些数据在表中的分布情况。例如,列可以具有均匀分布的不同值的数量,或者它可以是倾斜的,80%的时间具有一组特定的值,无论分布是否会随着时间频繁变化或者是相当静态的。这将使您更好地了解如何构建有效的查询。但是,当调试查询性能有一个基础,可以建立一个关于它为什么慢或低效的假设。

答案 2 :(得分:24)

要添加OPTION(RECOMPILE)非常有帮助的优秀列表(由@CodeCowboyOrg提供),

  1. 表变量。 使用表变量时,表变量不会有任何预先构建的统计信息,通常会导致查询计划中估计行与实际行之间存在较大差异。对具有表变量的查询使用OPTION(RECOMPILE)允许生成对所涉及的行数有更好估计的查询计划。我特别关键地使用了一个无法使用的表变量,而且我要放弃它,直到我添加了OPTION(RECOMPILE)。运行时间从几小时到几分钟。这可能是不寻常的,但无论如何,如果你使用表变量并进行优化,那么值得看看OPTION(RECOMPILE)是否有所作为。

答案 3 :(得分:0)

调整查询之前的第一个操作是碎片整理/重建索引和统计信息,否则你会浪费时间。

您必须检查执行计划以确定它是否稳定(更改参数时是否相同),否则,您可能必须创建封面索引(在本例中为每个表)(知道你可以创建一个对其他查询也有用的系统。)

作为一个例子:   创建索引idx01_datafeed_trans    在datafeed_trans(feedid,feedDate)       INCLUDE(acctNo,tradeDate)

如果计划稳定或者您可以稳定它,您可以使用sp_executesql(' sql语句')执行该句子以保存和使用固定的执行计划。

如果计划不稳定,您必须使用临时声明或EXEC(' sql语句')来评估和创建每次执行计划。 (或存储过程"重新编译")。

希望它有所帮助。

答案 4 :(得分:0)

解决了这个问题,但有一种解释似乎没有人考虑过。

统计信息-统计信息不可用或具有误导性

如果满足以下所有条件:

  1. feedid和feedDate列可能高度相关(例如,Feed ID比Feed日期更具体,并且date参数是冗余信息)。
  2. 没有将两个列都作为连续列的索引。
  3. 这两个列都没有手动创建的统计信息。

然后,sql服务器可能会错误地假设这些列是不相关的,从而导致应用限制和选择不良执行计划的基数估计值低于预期。在这种情况下,解决方法是创建一个链接两列的统计对象,这不是一个昂贵的操作。