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