我需要使用相同的查询两次,但是where子句略有不同。我想知道用一个比特值简单地调用相同的存储过程是否有效,并且有一个IF ... ELSE ...语句,决定要比较哪些字段。
或者我应该根据应用程序中的逻辑制作两个存储过程并调用每个过程?
我想更详细地了解这一点,但要正确理解。 如何为此编译执行计划?每个IF中的每个代码块都有一个...... ELSE ......?
或者它被编译为一个大的执行计划?
答案 0 :(得分:3)
关注正在缓存的执行计划是正确的。
Martin提供了一个很好的示例,表明该计划已缓存,并将在第一次执行时针对逻辑的某个分支进行优化。 在第一次执行之后,即使使用不同的参数调用存储过程(sproc)导致执行流程选择另一个分支,也会重用该计划。 这非常糟糕,会扼杀性能。我已经多次看到过这种情况,找到根本原因需要一段时间。
这背后的原因被称为"参数嗅探" ,值得研究。
一个常见的解决方案(我不建议的解决方案)是将你的sproc分成几个小的。 如果在sproc中调用sproc,内部sproc将获得针对传递给它的参数优化的执行计划。
如果没有充分的理由(一个很好的理由是模块化)将sproc分成几个较小的是一个丑陋的解决方法。 Martin表示,通过引入对模式的更改,可以重新编译语句。 我会在语句的末尾使用OPTION(RECOMPILE)。这指示优化器在考虑所有变量的当前值的情况下进行语句重新编译:不仅考虑参数,还考虑局部变量,这可以区分好计划和坏计划。
回到你根据参数使用不同的where子句构造查询的问题。我会使用以下模式:
WHERE
(@parameter1 is null or col1 = @parameter1 )
AND
(@parameter2 is null or col2 = @parameter2 )
...
OPTION (RECOMPILE)
缺点是该语句的执行计划从未被缓存(它不会影响到语句的缓存),如果sproc在编译时多次执行会产生影响现在应该考虑时间。如果出现问题,使用生产质量数据进行测试将为您提供答案。
好处是你可以编写可读和优雅的sprocs代码,而不是将优化器设置在错误的脚上。
要记住的另一个选择是,您可以在sproc级别(而不是语句级别)级别禁用执行计划缓存,该级别不太精细,更重要的是,在不考虑局部变量的值时优化
更多信息,请访问 http://www.sommarskog.se/dyn-search-2005.html http://sqlinthewild.co.za/index.php/2009/03/19/catch-all-queries/
答案 1 :(得分:1)
使用传递给过程的参数的初始值编译一次。虽然某些语句可能会延迟编译,但在这种情况下,它们将使用最终编译时的参数值进行编译。
您可以通过运行以下内容并查看实际执行计划来了解此信息。
CREATE TABLE T
(
C INT
)
INSERT INTO T
SELECT 1 AS C
UNION ALL
SELECT TOP (1000) 2
FROM master..spt_values
UNION ALL
SELECT TOP (1000) 3
FROM master..spt_values
GO
CREATE PROC P @C INT
AS
IF @C = 1
BEGIN
SELECT '1'
FROM T
WHERE C = @C
END
ELSE IF @C = 2
BEGIN
SELECT '2'
FROM T
WHERE C = @C
END
ELSE IF @C = 3
BEGIN
CREATE TABLE #T
(
X INT
)
INSERT INTO #T
VALUES (1)
SELECT '3'
FROM T,
#T
WHERE C = @C
END
GO
EXEC P 1
EXEC P 2
EXEC P 3
DROP PROC P
DROP TABLE T
运行2
案例显示来自T
的估计行数为1
而不是1000
,因为该语句是根据传入的初始参数值编译的。 1
。运行3
案例会给出准确的估计计数1000
,因为对(尚未创建)临时表的引用意味着该语句受延迟编译的约束。