有没有办法延迟编译存储过程的执行计划?

时间:2010-04-14 20:49:09

标签: sql sql-server sql-server-2005 tsql parameter-sniffing

(乍一看,这可能看起来像是Different execution plan when executing statement directly and from stored procedureWhy does the SqlServer optimizer get so confused with parameters?的副本,但我的实际问题有点不同)

好吧,这个让我难过了几个小时。我的例子是荒谬的抽象,所以我怀疑它是否可以在本地重新创建,但它为我的问题提供了上下文(另外,我正在运行SQL Server 2005)。

我有一个存储过程基本上有两个步骤,构建一个临时表,用很少的行填充它,然后查询一个非常大的表加入该临时表。它有多个参数,但最相关的是datetime@MinDate。”基本上:

create table #smallTable (ID int)

insert into #smallTable
select (a very small number of rows from some other table)

select * from aGiantTable
inner join #smallTable on #smallTable.ID = aGiantTable.ID
inner join anotherTable on anotherTable.GiantID = aGiantTable.ID
where aGiantTable.SomeDateField > @MinDate

如果我只是将其作为普通查询执行,通过将@MinDate声明为局部变量并运行它,它会生成一个执行速度非常快的最佳执行计划(首先加入#smallTable然后只考虑一个来自aGiantTable的一小部分行,同时执行其他操作)。它似乎意识到#smallTable很小,所以开始使用它会很有效。这很好。

但是,如果我使用@MinDate作为参数创建存储过程,则会产生完全低效的执行计划。 (我每次都在重新编译它,所以这不是一个糟糕的缓存计划......至少,我当然希望不是这样)

但是这里变得奇怪。如果我将proc更改为以下内容:

declare @LocalMinDate datetime
set @LocalMinDate = @MinDate --where @MinDate is still a parameter

create table #smallTable (ID int)

insert into #smallTable
select (a very small number of rows from some other table)

select * from aGiantTable
inner join #smallTable on #smallTable.ID = aGiantTable.ID
inner join anotherTable on anotherTable.GiantID = aGiantTable.ID
where aGiantTable.SomeDateField > @LocalMinDate

然后它给了我有效的计划!


所以我的理论是这样的:当作为普通查询(而不是存储过程)执行时,它等待构造昂贵查询的执行计划直到最后一分钟,因此查询优化器知道#smallTable很小并且使用该信息来提供有效的计划。

但是当作为存储过程执行时,它会立即创建整个执行计划,因此它无法使用这些信息来优化计划。

但为什么使用本地声明的变量会改变这个?为什么这会延迟执行计划的创建?那真的是发生了什么吗?如果是这样,有没有办法强制延迟编译(如果这确实是这里发生的事情)即使不以这种方式使用局部变量?

更一般地说,是否有人知道何时为存储过程的每个步骤创建执行计划?谷歌搜索没有提供任何有用的信息,但我认为我没有找到正确的事情。或者我的理论完全没有根据?

编辑:自发布以来,我已经学会了参数嗅探,我认为这是导致执行计划过早编译的原因(除非存储过程确实一次编译完毕),所以我的问题遗体 - 你可以强迫延迟吗?或完全禁用嗅探?

问题是学术问题,因为我可以用{/ 1}替换

来强制实施更有效的计划
select * from aGiantTable

或者只是吮吸它并屏蔽参数,但是,这种不一致让我非常好奇。


TL; DNR

这是一个非常长的问题,所以简而言之:

首次调用存储过程时,还是在执行存储过程时,是否创建了完整的执行计划?也就是说,如果存储过程包含多个步骤,那么是在首次调用过程时创建的每个步骤的执行计划,还是仅在过去的步骤完成执行后再次创建(再次,第一次调用它)?

2 个答案:

答案 0 :(得分:2)

这是参数嗅探,如果您没有SQL Server 2008和OPTIMIZE FOR UNKNOWN,那么使用局部变量(如您所找到的)屏蔽参数是您最好的选择。

答案 1 :(得分:1)

您可以查看一些其他文章:

http://blogs.msdn.com/queryoptteam/archive/2006/03/31/565991.aspx http://sqlblog.com/blogs/ben_nevarez/archive/2009/08/27/the-query-optimizer-and-parameter-sniffing.aspx

请注意,您还可以使用“重新编译”查询选项来解决“参数嗅探”