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


169

我遇到了一种奇怪的情况 OPTION (RECOMPILE)查询到查询中会使查询在半秒钟内运行,而省略查询会使查询花费超过五分钟的时间。

从Query Analyzer或C#程序通过执行查询时,就是这种情况SqlCommand.ExecuteReader()。打电话(或不打电话)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属性传递。

为了便于讨论,您可以假设参数从不改变,因此我们可以排除次佳的气味。


3
查询的参数是什么?查看这篇文章。 blogs.msdn.com/b/turgays/archive/2013/09/10/… 基本上,SQL在首次编译proc时会尝试根据参数生成查询计划。当您开始传递不同的,可能更现实的参数时,它可能会生成并非最佳的计划
Sparky 2014年

3
查询是否足够简洁,可以在此处列出?我认为Sparky是正确的,并且可能与参数嗅探有关,在阅读这篇出色的文章之前,我遇到了一个类似的问题,这使我很困惑:sommarskog.se/query-plan-mysteries.html
Chris

1
但是在这种情况下(出于测试目的),我总是传递相同的参数。没有其他应用程序可以潜入并使用其他参数来调用查询。感谢您的文章。会审查。
2014年

2
这可能是因为它嗅探参数和变量的值,或者是因为它简化了。更大的简化的例子会崩溃X = @X OR @X IS NULLX=@X并执行寻道看到这里推谓词进一步下跌对视图与窗口功能
马丁·史密斯

3
编辑后,查询分析器示例将使用变量,而不是参数。这些的价值永远不会被嗅到RECOMPILE。无论如何,请捕获执行计划并查看差异。
马丁·史密斯

Answers:


157

有时候使用OPTION(RECOMPILE)是有意义的。以我的经验,这是唯一可行的选择,是在使用动态SQL时。在探讨这种情况对您是否有意义之前,我建议您重新构建统计信息。这可以通过运行以下命令来完成:

EXEC sp_updatestats

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

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

创建存储过程时(我怀疑您是从.NET调用临时sql,但如果使用的是参数化查询,则最终将是存储proc调用)SQL Server尝试确定此查询的最有效执行计划根据数据库中的数据和传入的参数(parameter sniffing),然后缓存此计划。这意味着,如果在数据库中有10条记录的地方创建查询,然后在有100,000,000条记录时执行查询,则缓存的执行计划可能不再是最有效的。

总结-我看不出有什么OPTION(RECOMPILE)好处。我怀疑您只需要更新统计信息和执行计划即可。根据您的情况,重建统计信息可能是DBA工作的重要组成部分。如果您在更新统计信息后仍然遇到问题,建议您发布两个执行计划。

并回答您的问题-是的,我想说,每次执行查询时最好的最佳选择是重新编译执行计划是非常不寻常的。


22
是的,sp_updatestats达到了目的。当您提到查询最初在具有10条记录的表上运行时,现在您已经大吃一惊了,现在该表具有数百万条记录。完全是我的情况。我没有在帖子中提及它,因为我认为这并不重要。令人着迷的东西。再次感谢。
乍得·德克

3
这是我发现使用表变量的唯一方法,因为SQL始终认为其中只有一个行。当它包含数千行时,就会成为问题。
亚历克斯·朱可夫斯基2016年

4
一个有趣的细节:更新统计信息会隐式地使所有使用这些统计信息的缓存计划无效,但前提是在update动作之后统计信息实际上发生了更改。因此,对于高度偏斜的只读表,似乎明确的方法OPTION (RECOMPILE)可能是唯一的解决方案。
Groo

141

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

  1. 统计-统计信息已过时。数据库存储有关表和索引的各个列中值类型的范围和分布的统计信息。这有助于查询引擎针对如何进行查询制定攻击计划,例如,使用哈希或查看整个集合来匹配表之间的键的方法类型。您可以在整个数据库或仅某些表或索引上调用更新统计信息。这会使查询从一次运行减慢到另一次运行,因为当统计信息过时时,查询计划可能对于同一查询的新插入或更改的数据不是最佳的(下面将详细说明)。立即在生产数据库上更新统计信息可能不合适,因为这会导致一些开销,速度变慢和滞后,具体取决于要采样的数据量。您也可以选择使用“全面扫描”或“采样”来更新统计信息。如果查看查询计划,则还可以使用以下命令查看使用中的索引的统计信息:DBCC SHOW_STATISTICS(表名,索引名)。这将显示查询计划所使用的键的分布和范围。

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

  3. 索引 -可能没有更改查询,但是其他地方的更改(例如删除非常有用的索引)使查询速度变慢。

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

  5. 逻辑逻辑,您的查询不再高效,对少量的行很好,但不再扩展。这通常涉及对查询计划的更深入的分析。例如,您不再可以批量处理事情,而必须分批处理并进行较小的提交,或者您的Cross Product适用于较小的集合,但是随着扩展规模的增加,它现在占用了CPU和内存,对于使用DISTINCT时,您正在为每一行调用一个函数,由于CASTING类型转换或NULLS或函数,因此键匹配不使用索引。这里的可能性太多。

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


2
谢谢你,朋友。这是极好的信息。当我最初发布问题时,我将无法理解您的回答,但现在对我来说非常有意义。
乍得·德克2014年

3
到目前为止,参数嗅探是我生存的最大祸根。直到面试问题失败,我什至不知道该命令。我对参数嗅探的解决方案始终是对参数值进行哈希处理,并附加“ AND {hash} = {hash}”,以使sql对于不同的值始终是不同的。骇客,但行得通。
杰里米·博伊德

27

要添加到OPTION(RECOMPILE)可能非常有用的情况的出色列表(由@CodeCowboyOrg提供),

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

1
我有5个表变量的查询。在我的机器上,它执行了半个多小时。在我同事的机器上,它在不到1秒的时间内执行。这些计算机具有相似的硬件和相同的SQL Server版本。如果我们都添加OPTION(RECOMPILE),则它将在两台计算机上执行2秒。在所有情况下,执行测试均在SSMS中进行。是什么造成了这种差异?
亚当

1
您可以在没有Option(重新编译)的情况下在您的计算机和您的同事计算机上比较它的执行计划吗?这可能表明差异的根源。
DWright

1
对于临时表,情况是否相同?
Muflix '16

1
@muflix:好问题。我相信临时表的效果不一样,因为它们具有统计信息,并且引擎应该像其他表一样进行自动重新编译选择,我相信(但不确定)。也许别人更确定地知道。
DWright

2
临时表中的统计信息不会自动更新或重新编译,因此程序员需要这样做。
J. Michael Wuerth

1

在修剪查询之前,首先要做的就是对索引和统计数据进行碎片整理/重建,否则会浪费时间。

您必须检查执行计划以查看其是否稳定(更改参数时是否相同),如果不稳定,则可能必须创建覆盖索引(在这种情况下为每个表)(知道该系统后,您可以创建一个对其他查询也很有用)。

例如:在datafeed_trans上创建索引idx01_datafeed_trans(feedid,feedDate) INCLUDE(acctNo,tradeDate)

如果计划是稳定的或可以使计划稳定,则可以使用sp_executesql('sql statement')执行语句以保存并使用固定的执行计划。

如果计划不稳定,则必须每次使用临时语句或EXEC('sql statement')评估并创建执行计划。(或“带重新编译”的存储过程)。

希望能帮助到你。


1

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

统计-无法获得统计数据或产生误导

如果满足以下所有条件:

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

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

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.