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

问题描述 投票:141回答:5

我遇到了一个奇怪的情况,在我的查询中附加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属性传入。

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

sql sql-server sql-server-2008 compilation hint
5个回答
138
投票

有时使用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工作的重要组成部分。如果您在更新统计数据后仍然遇到问题,我建议发布两个执行计划。

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


125
投票

通常,当查询的运行与运行之间存在巨大差异时,我发现它通常是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. 逻辑它的逻辑,你的查询不再有效,它适用于少量行,但不再是缩放。这通常涉及对查询计划进行更深入的分析。例如,您不能再批量处理,但必须使用Chunk的东西并进行较小的Commits,或者您的Cross Product适用于较小的集合,但现在占用CPU和内存,因为它扩展得更大,这也可能适用于使用DISTINCT,您正在为每一行调用一个函数,由于CASTING类型转换或NULLS或函数,您的密钥匹配不使用索引......此处有太多可能性。

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


24
投票

要添加OPTION(RECOMPILE)非常有用的优秀列表(由@CodeCowboyOrg给出),

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

0
投票

在运行查询之前的第一个操作是碎片整理/重建索引和统计信息,这是您浪费时间的另一种方式。

你必须检查执行计划以确定它是否稳定(当你更改参数时是相同的),如果没有,你可能必须创建一个封面索引(在这种情况下为每个表)(知道系统你可以创建一个对于其他查询也很有用)。

例如:create index idx01_datafeed_trans On datafeed_trans(feedid,feedDate)INCLUDE(acctNo,tradeDate)

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

如果计划不稳定,您必须使用临时声明或EXEC('sql sentence')来评估和创建每次执行计划。 (或存储过程“with recompile”)。

希望能帮助到你。


0
投票

提到这个问题,但有一个解释,似乎没有人考虑过。

统计 - 统计数据不可用或具有误导性

如果满足以下所有条件:

  1. 列feedid和feedDate可能高度相关(例如,feed id比feed日期更具体,而date参数是冗余信息)。
  2. 没有索引将两列都作为顺序列。
  3. 没有手动创建的统计信息涵盖这两列。

然后sql server可能错误地假设列是不相关的,导致低于预期的基数估计值来应用这两个限制并且选择了差的执行计划。这种情况下的修复方法是创建一个链接两列的统计对象,这不是一项昂贵的操作。

© www.soinside.com 2019 - 2024. All rights reserved.