如果参数存储在局部变量中,则更好的执行计划

Leo*_*eon 8 performance sql-server stored-procedures execution-plan parameter query-performance

我有两个存储过程。这个速度非常快(约 2 秒)

CREATE PROCEDURE [schema].[Test_fast]
   @week date
AS
BEGIN

    declare @myweek date = @week

    select distinct serial 
    from [schema].[tEventlog]         as e
    join [schema].tEventlogSourceName as s on s.ID = e.FKSourceName
    where s.SourceName = 'source_name'
        and (e.EventCode = 1 or e.EventCode = 9)
        and cast(@myweek as datetime2(3)) <= [Date] 
        and [Date] < dateadd(day, 7, cast(@myweek as datetime2(3)))    
END
Run Code Online (Sandbox Code Playgroud)

而这个运行缓慢(~ 2 小时):

create PROCEDURE [schema].[Test_slow]
   @week date
AS
BEGIN

    select distinct serial 
    from [schema].[tEventlog]         as e
    join [schema].tEventlogSourceName as s on s.ID = e.FKSourceName
    where s.SourceName = 'source_name'
        and (e.EventCode = 1 or e.EventCode = 9)
        and cast(@week as datetime2(3)) <= [Date] 
        and [Date] < dateadd(day, 7, cast(@week as datetime2(3)))
END
Run Code Online (Sandbox Code Playgroud)

唯一真正的区别是行(使用局部变量@myweek):

declare @myweek date = @week
Run Code Online (Sandbox Code Playgroud)

以下是执行计划。第一个计划来自 [schema].[Test_fast],第二个来自 [schema].[Test_slow]:

在此处输入图片说明

我的问题是:为什么 SQL Server 2012 在我取参数并将其存储在局部变量中,然后使用该局部变量时,会得到更好的执行计划(更快)。统计数据或索引是否有问题?(我也想知道为什么第二个执行计划没有使用任何类型的并行执行)。

更新

我为 2 个 SP 提供了相同的参数并在同一时间(接近 2 秒的时间差异)启动了它们,这不是此数据库中统计信息的自动更新。

例子:

EXEC    [schema].[Test_fast]
        @week = '2016-02-08'

EXEC    [schema].[Test_slow]
        @week = '2016-02-08'
Run Code Online (Sandbox Code Playgroud)

这是执行计划:

https://gist.github.com/anonymous/6e404f896d9613c2061a#file-sp_execution_plan-sqlplan

索引的额外更新也没有效果。

Pau*_*ite 16

使用局部变量可以防止嗅探参数值,因此查询是根据平均分布统计信息编译的。这是解决方法对于某些类型的参数灵敏度问题之前OPTION (OPTIMIZE FOR UNKNOWN)跟踪标志4136面世。

从提供的执行计划来看,这正是您的案例中发生的情况。

使用局部变量时,不能嗅探变量中的值:

没有嗅探

注意空白的“编译值”。查询优化器根据Date列中值的平均分布(或可能是完整的猜测)估计更多的行数,导致并行计划。

直接使用存储过程参数时,嗅探@week的值:

嗅了嗅

优化器使用值“2016-02-08”估计将匹配查询谓词的行数,插入到:

and cast(@week as datetime2(3)) <= [Date] 
and [Date] < dateadd(day, 7, cast(@week as datetime2(3)))
Run Code Online (Sandbox Code Playgroud)

它得出一行的估计值,导致选择带有键查找的串行计划。上面的谓词对于基数估计不是很友好,所以1行估计可能不是很准确。您可以尝试启用跟踪标志 4199,但不能保证估计值会提高。

有关更多详细信息,请参阅:

参数嗅探、嵌入和RECOMPILE选项

通常,存储过程的初始运行也有可能以非常有选择性的 @week 值发生,而预期的行数很少。另一个可能的问题原因是在初始调用中使用了最近的 @week 值时,在更新统计信息以涵盖该值范围之前(这是Ascending Key Problem)。

@week 非常有选择性的嗅探值可能会导致查询优化器选择具有索引查找和键查找的非并行计划。该计划将被缓存以供将来执行具有不同参数值的过程时重用。如果稍后执行(@week 的值不同)选择的行比原来多得多,则该计划的执行效果可能很差,因为搜索 + 键查找不再是一个好的策略。