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

时间:2013-01-22 21:45:17

标签: sql sql-server-2008 tsql stored-procedures query-optimization

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

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

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

3 个答案:

答案 0 :(得分:9)

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

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

此查询:

SELECT *
FROM T
WHERE A = '20130122';

将产生不同的执行计划:

SELECT *
FROM T
WHERE A = '20130123';

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

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

CREATE PROCEDURE dbo.GetFromT @Param DATE
AS
    SELECT *
    FROM T
    WHERE A = @Param

然后运行

EXECUTE dbo.GetFromT '20130122'; --400 rows

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

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

然后运行:

EXECUTE dbo.GetFromT '20130122';

虽然程序一次编译,但它没有正确流动,因此在第一次编译时创建的查询计划不知道@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

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

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';

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

在为重新编译设置过程并再次运行(按不同顺序注释)之后,GetFromT使用书签循环,并保留“20130122”的计划,尽管之前认为表扫描是一个更合适的计划。 GetFromT2不受订单影响,并且与重新计算之前的计划相同。

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


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

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

答案 1 :(得分:2)

确实有帮助。

以下链接包含有关参数嗅探的更多详细信息。

http://blogs.msdn.com/b/turgays/archive/2013/09/10/parameter-sniffing-problem-and-workarounds.aspx

http://sqlperformance.com/2013/08/t-sql-queries/parameter-sniffing-embedding-and-the-recompile-options

第一次执行带参数的SP时,查询优化器会根据参数的值创建查询计划。 查询优化器使用该特定值的统计数据来确定最佳查询计划。但基数问题会影响这一点。这意味着如果您执行具有不同参数值的相同SP,则先前生成的查询计划可能不是最佳计划。

通过为本地变量分配参数,我们隐藏了查询优化器中的参数值。因此,它为一般情况创建了查询计划。

这与使用" OPTIMIZE FOR UNKNOWN"相同。提示SP。

答案 2 :(得分:1)

我不相信。现代计算机体系结构在处理器附近有足够的高速缓存,用于存储存储过程值。从本质上讲,您可以将它们视为存在于“堆栈”中,并将其加载到本地缓存中。

如果您有输出参数,那么可能将输入值复制到局部变量将消除一步间接。但是,第一次执行间接寻址时,目标内存将放在本地缓存中,它可能会保留在那里。

所以,不,我不认为这是一个重要的优化。

但是,您可以随时计算存储过程的不同变体,看看这是否有用。