将存储过程输入参数分配给局部变量是否有助于优化查询?

Lea*_*ner 1 sql t-sql stored-procedures query-optimization sql-server-2008

我有一个存储过程,需要5个输入参数.该过程有点复杂,需要大约2分钟才能执行.我正在优化查询.

所以,我的问题是,它是否总是有助于将输入参数分配给局部变量,然后在过程中使用局部变量?

如果是这样,它有什么帮助?

Gar*_*thD 10

我不会尝试解释参数嗅探的全部细节,但简而言之,它并不总是有帮助(它可以阻碍).

想象一个带有主键和索引日期列(A)的表(T),在表中有1,000行,400具有相同的A值(比如说今天20130122),剩下的600行是接下来的600天,所以每个日期只有1条记录.

这个查询:

SELECT *
FROM T
WHERE A = '20130122';
Run Code Online (Sandbox Code Playgroud)

将产生不同的执行计划:

SELECT *
FROM T
WHERE A = '20130123';
Run Code Online (Sandbox Code Playgroud)

由于统计信息将表明,对于将返回1,000行中的前400个,优化器应该认识到表扫描比书签查找更有效,而第二个只会产生1行,因此书签查找将会很多更高效.

现在,回到你的问题,如果我们做了一个程序:

CREATE PROCEDURE dbo.GetFromT @Param DATE
AS
    SELECT *
    FROM T
    WHERE A = @Param
Run Code Online (Sandbox Code Playgroud)

然后跑

EXECUTE dbo.GetFromT '20130122'; --400 rows
Run Code Online (Sandbox Code Playgroud)

将使用带有表扫描的查询计划,如果您第一次运行它,则使用'20130123'作为参数,它将存储书签查找计划.在程序重新编译之前,计划将保持不变.做这样的事情:

CREATE PROCEDURE dbo.GetFromT @Param VARCHAR(5)
AS
    DECLARE @Param2 VARCHAR(5) = @Param;
    SELECT *
    FROM T
    WHERE A = @Param2
Run Code Online (Sandbox Code Playgroud)

然后运行:

EXECUTE dbo.GetFromT '20130122';
Run Code Online (Sandbox Code Playgroud)

虽然程序一次编译,但它没有正确流动,因此在第一次编译时创建的查询计划不知道@Param2将与@param相同,所以优化器(不知道有多少行到期望)将假设300将被返回(30%),因为这将认为表扫描更有效,书签查找.如果您使用'20130123'作为参数运行相同的过程,它将产生相同的计划(无论它首先调用哪个参数),因为统计信息不能用于unkonwn值.因此,为'20130122'运行此过程会更有效,但是对于所有其他值,效率将低于没有本地参数(假设没有本地参数的过程首先使用"20130122"之外的任何内容调用)


一些要展示的查询,以便您可以自己查看执行计划

创建架构和示例数据

CREATE TABLE T (ID INT IDENTITY(1, 1) PRIMARY KEY, A DATE NOT NULL, B INT,C INT, D INT, E INT);

CREATE NONCLUSTERED INDEX IX_T ON T (A);

INSERT T (A, B, C, D, E)
SELECT  TOP 400 CAST('20130122' AS DATE), number, 2, 3, 4 
FROM    Master..spt_values 
WHERE   type = 'P'
UNION ALL
SELECT TOP 600 DATEADD(DAY, number, CAST('20130122' AS DATE)), number, 2, 3, 4 
FROM    Master..spt_values 
WHERE   Type = 'P';
GO
CREATE PROCEDURE dbo.GetFromT @Param DATE
AS
    SELECT *
    FROM T
    WHERE A = @Param
GO
CREATE PROCEDURE dbo.GetFromT2 @Param DATE
AS
    DECLARE @Param2 DATE = @Param;
    SELECT *
    FROM T
    WHERE A = @Param2
GO
Run Code Online (Sandbox Code Playgroud)

运行程序(显示实际执行计划):

EXECUTE GetFromT '20130122';
EXECUTE GetFromT '20130123';
EXECUTE GetFromT2 '20130122';
EXECUTE GetFromT2 '20130123';
GO
EXECUTE SP_RECOMPILE GetFromT;
EXECUTE SP_RECOMPILE GetFromT2;
GO
EXECUTE GetFromT '20130123';
EXECUTE GetFromT '20130122';
EXECUTE GetFromT2 '20130123';
EXECUTE GetFromT2 '20130122';
Run Code Online (Sandbox Code Playgroud)

您将看到第一次GetFromT编译它使用表扫描,并在使用参数'20130122'运行时保留它,GetFromT2也使用表扫描并保留'20130122'的计划.

在设置了重新编译并再次运行的程序之后(以不同的顺序记录),GetFromT使用书签循环,并保留"20130122"的计划,尽管之前认为表扫描是更合适的计划.GetFromT2不受订单影响,并且具有与重新编译之前相同的计划.

因此,总而言之,它取决于数据的分布,索引,重新编译的频率,以及程序是否会从使用局部变量中受益的一点运气.它当然并不总是有帮助.


希望我已经阐明了使用本地参数,执行计划和存储过程complilation的效果.如果我完全失败了,或者错过了关键点,可以在这里找到更深入的解释:

http://www.sommarskog.se/query-plan-mysteries.html