SQL Server 2008:为什么在首先满足另一个逻辑条件时进行表扫描?

发布于 2024-09-19 16:22:05 字数 397 浏览 5 评论 0 原文

考虑下面的代码:

declare @var bit = 0

select * from tableA as A
where
1=
(case when @var = 0 then 1
      when exists(select null from tableB as B where A.id=B.id) 
      then 1
      else 0
end)

由于变量 @var 设置为 0,因此计算搜索的 case 运算符的结果为 1。在 case 文档中写道,直到第一个 WHEN 为 TRUE 时才会计算它。但是当我查看执行计划时,我发现 tableB 也被扫描了。

有谁知道为什么会发生这种情况?当另一个逻辑条件评估为 TRUE 时,可能有一些方法可以避免第二次表扫描?

Consider following piece of code:

declare @var bit = 0

select * from tableA as A
where
1=
(case when @var = 0 then 1
      when exists(select null from tableB as B where A.id=B.id) 
      then 1
      else 0
end)

Since variable @var is set to 0, then the result of evaluating searched case operator is 1. In the documentation of case it is written that it is evaluated until first WHEN is TRUE. But when I look at execution plan, I see that tableB is scanned as well.

Does anybody know why this happens? Probably there are ways how one can avoid second table scan when another logical condition is evaluated to TRUE?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

情何以堪。 2024-09-26 16:22:05

因为编译和缓存的计划需要适用于 @var 的所有可能值,所以

您需要使用类似

if (@var = 0)
select * from tableA 
else
select * from tableA as A
where exists(select * from tableB as B where A.id=B.id) 

Even OPTION RECOMPILE 的东西,看起来也没有帮助实际上。它仍然无法为您提供通过文字 0=0

declare @var bit = 0

select * from 
master.dbo.spt_values  as A
where
1=
(case when 0 = @var then 1
      when exists(select null from master.dbo.spt_values as B where A.number=B.number) 
      then 1
      else 0
end)
option(recompile)

计划 http://img189.imageshack.us/img189/3977/executionplan.jpg

select * from 
master.dbo.spt_values  as A
where
1=
(case when 0 = 0 then 1
      when exists(select null from master.dbo.spt_values as B where A.number=B.number) 
      then 1
      else 0
end)

计划 http://img193.imageshack.us/img193/3977/executionplan.jpg

RE:评论中的问题。在启用“包括实际执行计划”选项的情况下尝试以下操作。

declare @var bit = datepart(second,GETDATE())%2

print @var

if (@var = 0)
select * from 
master.dbo.spt_values  --8BA71BA5-3025-4967-A0C8-38B9FBEF8BAD
else
select * from 
master.dbo.spt_values  as A --8BA71BA5-3025-4967-A0C8-38B9FBEF8BAD
where exists(select null from master.dbo.spt_values as B where A.number=B.number) 

然后尝试

SELECT usecounts, cacheobjtype, objtype, text, query_plan
FROM sys.dm_exec_cached_plans 
CROSS APPLY sys.dm_exec_sql_text(plan_handle) 
CROSS APPLY sys.dm_exec_query_plan(plan_handle) 
where text like '%8BA71BA5-3025-4967-A0C8-38B9FBEF8BAD%'

编译的计划看起来像

计划 http://img178.imageshack.us/img178 /3977/executionplan.jpg

实际执行计划将仅显示实际执行的一条路径。

Because the plan that is compiled and cached needs to work for all possible values of @var

You would need to use something like

if (@var = 0)
select * from tableA 
else
select * from tableA as A
where exists(select * from tableB as B where A.id=B.id) 

Even OPTION RECOMPILE doesn't look like it would help actually. It still doesn't give you the plan you would have got with a literal 0=0

declare @var bit = 0

select * from 
master.dbo.spt_values  as A
where
1=
(case when 0 = @var then 1
      when exists(select null from master.dbo.spt_values as B where A.number=B.number) 
      then 1
      else 0
end)
option(recompile)

Plan http://img189.imageshack.us/img189/3977/executionplan.jpg

select * from 
master.dbo.spt_values  as A
where
1=
(case when 0 = 0 then 1
      when exists(select null from master.dbo.spt_values as B where A.number=B.number) 
      then 1
      else 0
end)

Plan http://img193.imageshack.us/img193/3977/executionplan.jpg

RE: Question in comments. Try the following with the "Include Actual Execution Plan" option enabled.

declare @var bit = datepart(second,GETDATE())%2

print @var

if (@var = 0)
select * from 
master.dbo.spt_values  --8BA71BA5-3025-4967-A0C8-38B9FBEF8BAD
else
select * from 
master.dbo.spt_values  as A --8BA71BA5-3025-4967-A0C8-38B9FBEF8BAD
where exists(select null from master.dbo.spt_values as B where A.number=B.number) 

Then try

SELECT usecounts, cacheobjtype, objtype, text, query_plan
FROM sys.dm_exec_cached_plans 
CROSS APPLY sys.dm_exec_sql_text(plan_handle) 
CROSS APPLY sys.dm_exec_query_plan(plan_handle) 
where text like '%8BA71BA5-3025-4967-A0C8-38B9FBEF8BAD%'

The Compiled Plan will look like

Plan http://img178.imageshack.us/img178/3977/executionplan.jpg

The Actual Execution Plan will show only one path was actually executed though.

囚我心虐我身 2024-09-26 16:22:05

如果 tableB 的行数很少,则表扫描是最快的方法。

动态搜索条件的最佳来源:

Erland Sommarskog 的 T-SQL 中的动态搜索条件

对于如何执行此操作(是否可以使用索引)有很多微妙的影响。如果您使用的是 SQL Server 2008 的正确版本,则只需将OPTION (RECOMPILE) 添加到查询中,运行时的局部变量值将用于优化。

考虑到这一点,OPTION (RECOMPILE) 将采用此代码(其中没有索引可以与这些混乱的 OR 一起使用):

WHERE
    (@search1 IS NULL or Column1=@Search1)
    AND (@search2 IS NULL or Column2=@Search2)
    AND (@search3 IS NULL or Column3=@Search3)

并在运行时对其进行优化(前提是只有 @Search2 传入了一个值):

WHERE
    Column2=@Search2

并且可以使用索引(如果您在 Column2 上定义了索引)

if tableB has few rows, a table scan is the fastest way to go.

best source for dynamic search conditions:

Dynamic Search Conditions in T-SQL by Erland Sommarskog

there are a lot of subtle implications on how you do this as to if an index can be used or not. If you are on the proper release of SQL Server 2008 you can just add OPTION (RECOMPILE) to the query and the local variable's value at run time is used for the optimizations.

Consider this, OPTION (RECOMPILE) will take this code (where no index can be used with this mess of ORs):

WHERE
    (@search1 IS NULL or Column1=@Search1)
    AND (@search2 IS NULL or Column2=@Search2)
    AND (@search3 IS NULL or Column3=@Search3)

and optimize it at run time to be (provided that only @Search2 was passed in with a value):

WHERE
    Column2=@Search2

and an index can be used (if you have one defined on Column2)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文