2

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

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

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

4

3 回答 3

11

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

想象一个表 (T) 有一个主键和一个索引日期列 (A),表中有 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 一样,所以优化器(不知道要多少行expect) 将假设将返回 300 (30%),因此认为表扫描比书签查找更有效。如果您使用“20130123”作为参数运行相同的过程,它将产生相同的计划(不管它首先使用什么参数调用),因为统计信息不能用于未知值。因此,为“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

于 2013-01-22T22:56:38.470 回答
2

我不相信。现代计算机体系结构在处理器附近有大量缓存,用于放入存储过程值。本质上,您可以将它们视为位于加载到本地缓存内存中的“堆栈”上。

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

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

但是,您总是可以对存储过程的不同变体进行计时,看看这是否有帮助。

于 2013-01-22T22:28:35.270 回答
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,则先前生成的查询计划可能不是最佳计划。

通过将参数分配给局部变量,我们对查询优化器隐藏了参数值。因此它为一般情况创建查询计划。

这与在 SP 中使用“OPTIMIZE FOR UNKNOWN”提示相同。

于 2014-10-06T21:50:41.837 回答