我需要两次使用相同的查询,但 where 子句略有不同。我想知道用一个位值简单地调用相同的存储过程是否有效,并有一个 IF...ELSE... 语句来决定要比较哪些字段。
或者我应该创建两个存储过程并根据我的应用程序中的逻辑调用每个过程?
我想更详细地了解这一点,但要正确理解。如何为此编制执行计划?每个 IF...ELSE... 中的每个代码块都有一个吗?
还是编译成一个大的执行计划?
我需要两次使用相同的查询,但 where 子句略有不同。我想知道用一个位值简单地调用相同的存储过程是否有效,并有一个 IF...ELSE... 语句来决定要比较哪些字段。
或者我应该创建两个存储过程并根据我的应用程序中的逻辑调用每个过程?
我想更详细地了解这一点,但要正确理解。如何为此编制执行计划?每个 IF...ELSE... 中的每个代码块都有一个吗?
还是编译成一个大的执行计划?
您担心被缓存的执行计划是正确的。
Martin 给出了一个很好的例子,表明该计划是缓存的,并且会在第一次执行时针对您的逻辑的某个分支进行优化。在第一次执行之后,即使您使用不同的参数调用存储过程 (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 被执行多次,这可能会产生影响,因为现在应该占用编译时间考虑到。使用生产质量数据执行测试将为您提供问题是否存在问题的答案。
好处是您可以编写可读且优雅的存储过程,而不会将优化器设置在错误的位置。
要记住的另一个选项是,您可以在 sproc 级别(相对于语句级别)禁用执行计划缓存,这种缓存粒度较小,更重要的是,在优化时不会考虑局部变量的值。
更多信息请访问 http://www.sommarskog.se/dyn-search-2005.html http://sqlinthewild.co.za/index.php/2009/03/19/catch-all-queries/
它使用传递给过程的参数的初始值编译一次。尽管某些语句可能会受到延迟编译,在这种情况下,它们将使用最终编译时的任何参数值进行编译。
您可以通过运行下面的代码并查看实际的执行计划来看到这一点。
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
as 1
not的估计行数,1000
因为该语句是根据传入的初始参数值编译的1
。运行该3
案例给出了准确的估计计数,1000
因为对(尚未创建的)临时表的引用意味着该语句受到延迟编译的影响。