我在SQL Server 2008 R2中执行了一个过程,脚本是:
DECLARE @LocalVar SMALLINT = GetLocalVarFunction();
SELECT
[TT].[ID],
[TT].[Title]
FROM [TargetTable] AS [TT]
LEFT JOIN [AcceccTable] AS [AT] ON [AT].[AccessID] = [TT].[ID]
WHERE
(
(@LocalVar = 1 AND ([AT].[Access] = 0 OR [AT].[Access] Is Null) AND
([TT].[Level] > 7)
);
GO
此程序在16
秒内执行。
但是,当我将Where子句更改为:
WHERE
(
((1=1) AND [AT].[Access] = 0 OR [AT].[Access] Is Null) AND
([TT].[Level] > 7)
);
程序执行时间不到1
秒。
如您所见,我只是删除了局部变量。
问题出在哪里?在where子句中使用局部变量是否有任何遗漏?当我在where子句中使用局部变量时,有什么建议可以改善执行时间吗?
更新
我还想在脚本之前添加if
语句并将过程拆分为2个过程,但是我有4或5个变量,如上所述并使用if
语句是如此复杂。
UPDATE2:
我更改了@LocalVar
:
DECLARE @LocalVar SMALLINT = 1;
执行时间没有变化。
答案 0 :(得分:2)
答案 1 :(得分:2)
当您在WHERE
过滤器中使用局部变量时,它会导致 FULL TABLE SCAN 。 SQL Server在编译时不知道局部变量的值。因此,SQL Server会为该列提供最大规模的执行计划。
正如您所看到的,当您通过1==1
时,SQL服务器知道该值,因此性能不会降低。但是,当你传递局部变量时,该值是未知的。
一种解决方案可能是在SQL查询结束时使用 OPTION(RECOMPILE)
您可以查看OPTIMIZE FOR UNKNOWN
答案 2 :(得分:0)
您似乎正在使用@LocalVar
作为分支条件,如下所示:
@LocalVar
为1,则将过滤器应用于查询@LocalVar
为0,则返回空结果集。IMO你最好明确地写这个条件,因为那时SQL可以优化2个分支的单独计划,即
DECLARE @LocalVar SMALLINT = GetLocalVarFunction();
IF (@LocalVar = 1)
SELECT
[TT].[ID],
[TT].[Title]
FROM [TargetTable] AS [TT]
LEFT JOIN [AcceccTable] AS [AT] ON [AT].[AccessID] = [TT].[ID]
WHERE
(
([AT].[Access] = 0 OR [AT].[Access] Is Null) AND
([TT].[Level] > 7)
)
ELSE
SELECT
[TT].[ID],
[TT].[Title]
FROM [TargetTable] AS [TT]
WHERE 1=2 -- Or any invalid filter, to retain the empty result
然后,因为现在存储过程中有2个分支,所以应该将WITH RECOMPILE
添加到存储过程中,因为2个分支的查询计划完全不同。
修改
只是为了澄清评论:
请注意,在查询后放置OPTION(RECOMPILE)
意味着query plan is never cached - 如果经常调用您的查询,这可能不是一个好主意。
PROC级别的WITH RECOMPILE
可防止通过proc缓存分支。 与查询级别的OPTION(RECOMPILE)
相同。
如果您的查询中有大量的过滤器排列,那么上面的“分支”技术就无法很好地扩展 - 您的代码很快就会变得无法维护。
然而,您可能需要考虑使用parameterized dynamic SQL。然后,SQL将至少为每个排列缓存一个单独的计划。