SELECT
如果我在一个IF EXISTS
条件中编写两个语句,AND
在这些选择查询之间有一个子句,即使第一个SELECT
返回 false,两个查询是否都会执行?
IF EXISTS (SELECT....) AND EXISTS(SELECT ....)
BEGIN
END
SQL Server 引擎在这种情况下是否同时执行 SQL 语句?
谢谢克里什
SELECT
如果我在一个IF EXISTS
条件中编写两个语句,AND
在这些选择查询之间有一个子句,即使第一个SELECT
返回 false,两个查询是否都会执行?
IF EXISTS (SELECT....) AND EXISTS(SELECT ....)
BEGIN
END
SQL Server 引擎在这种情况下是否同时执行 SQL 语句?
谢谢克里什
我会将测试重写为
IF CASE
WHEN EXISTS (SELECT ...) THEN CASE
WHEN EXISTS (SELECT ...) THEN 1
END
END = 1
这保证了此处描述的短路,但确实意味着您需要选择最便宜的一个进行预先评估,而不是将其留给优化器。
在我下面的极其有限的测试中,以下似乎在测试时适用
EXISTS AND EXISTS
该EXISTS AND EXISTS
版本似乎最有问题。这将一些外部半连接链接在一起。在任何情况下,它都没有重新安排测试的顺序,以尝试先进行更便宜的测试(本文后半部分讨论了这个问题)。在IF ...
版本中,如果它没有短路,它不会有任何区别。然而,当这个组合谓词放在一个WHERE
子句中时,计划会发生变化并且它确实会短路,因此重新排列可能是有益的。
/*All tests are testing "If False And False"*/
IF EXISTS(SELECT COUNT(*) FROM master..spt_monitor HAVING COUNT(*)=2)
AND EXISTS (SELECT COUNT(*) FROM master..spt_values HAVING COUNT(*)=1)
PRINT 'Y'
/*
Table 'spt_values'. Scan count 1, logical reads 9
Table 'spt_monitor'. Scan count 1, logical reads 1
*/
IF EXISTS (SELECT COUNT(*) FROM master..spt_values HAVING COUNT(*)=1)
AND EXISTS(SELECT COUNT(*) FROM master..spt_monitor HAVING COUNT(*)=2)
PRINT 'Y'
/*
Table 'spt_monitor'. Scan count 1, logical reads 1
Table 'spt_values'. Scan count 1, logical reads 9
*/
SELECT 1
WHERE EXISTS(SELECT COUNT(*) FROM master..spt_monitor HAVING COUNT(*)=2)
AND EXISTS (SELECT COUNT(*) FROM master..spt_values HAVING COUNT(*)=1)
/*
Table 'Worktable'. Scan count 0, logical reads 0
Table 'spt_monitor'. Scan count 1, logical reads 1
*/
SELECT 1
WHERE EXISTS (SELECT COUNT(*) FROM master..spt_values HAVING COUNT(*)=1)
AND EXISTS(SELECT COUNT(*) FROM master..spt_monitor HAVING COUNT(*)=2)
/*
Table 'Worktable'. Scan count 0, logical reads 0
Table 'spt_values'. Scan count 1, logical reads 9
*/
所有这些的计划看起来都非常相似。SELECT 1 WHERE ...
版本和版本之间行为差异的原因IF ...
是,对于前者,如果条件为假,那么正确的行为是不返回任何结果,因此它只是链接OUTER SEMI JOINS
,如果一个为假,则零行结转到下一个。
然而,IF
版本总是需要返回 1 或 0 的结果。该计划在其外部连接中使用探测列,如果EXISTS
测试未通过,则将其设置为 false(而不是简单地丢弃该行)。这意味着总是有 1 行馈入下一个 Join 并且它总是被执行。
该CASE
版本有一个非常相似的计划,但它使用一个谓词,如果不满足PASSTHRU
先前的条件,它会使用该谓词跳过执行 JOIN 。THEN
我不确定为什么 combine AND
s 不会使用相同的方法。
EXISTS OR EXISTS
该EXISTS OR EXISTS
版本使用连接 ( UNION ALL
) 运算符作为外部半联接的内部输入。这种安排意味着它可以在第一个返回后立即停止从内侧请求行(即它可以有效地短路)所有 4 个查询都以相同的计划结束,其中首先评估了更便宜的谓词。
/*All tests are testing "If True Or True"*/
IF EXISTS(SELECT COUNT(*) FROM master..spt_monitor HAVING COUNT(*)=1)
OR EXISTS (SELECT COUNT(*) FROM master..spt_values HAVING COUNT(*)<>1)
PRINT 'Y'
/*
Table 'Worktable'. Scan count 0, logical reads 0
Table 'spt_monitor'. Scan count 1, logical reads 1
*/
IF EXISTS (SELECT COUNT(*) FROM master..spt_values HAVING COUNT(*)<>1)
OR EXISTS(SELECT COUNT(*) FROM master..spt_monitor HAVING COUNT(*)= 1)
PRINT 'Y'
/*
Table 'Worktable'. Scan count 0, logical reads 0
Table 'spt_monitor'. Scan count 1, logical reads 1
*/
SELECT 1
WHERE EXISTS(SELECT COUNT(*) FROM master..spt_monitor HAVING COUNT(*)= 1)
OR EXISTS (SELECT COUNT(*) FROM master..spt_values HAVING COUNT(*)<>1)
/*
Table 'Worktable'. Scan count 0, logical reads 0
Table 'spt_monitor'. Scan count 1, logical reads 1
*/
SELECT 1
WHERE EXISTS (SELECT COUNT(*) FROM master..spt_values HAVING COUNT(*)<>1)
OR EXISTS(SELECT COUNT(*) FROM master..spt_monitor HAVING COUNT(*)=1)
/*
Table 'Worktable'. Scan count 0, logical reads 0
Table 'spt_monitor'. Scan count 1, logical reads 1
*/
ELSE
我确实想到尝试将德摩根定律转换AND
为OR
并看看这是否有任何不同。转换第一个查询给出
IF NOT ((NOT EXISTS(SELECT COUNT(*) FROM master..spt_monitor HAVING COUNT(*)=2)
OR NOT EXISTS (SELECT COUNT(*) FROM master..spt_values HAVING COUNT(*)=1)))
PRINT 'Y'
ELSE
PRINT 'N'
/*
Table 'spt_monitor'. Scan count 1, logical reads 1
Table 'spt_values'. Scan count 1, logical reads 9
*/
所以这仍然对短路行为没有任何影响。但是,如果您删除NOT
并颠倒IF ... ELSE
条件的顺序,它现在会短路!
IF (NOT EXISTS(SELECT COUNT(*) FROM master..spt_monitor HAVING COUNT(*)=2)
OR NOT EXISTS (SELECT COUNT(*) FROM master..spt_values HAVING COUNT(*)=1))
PRINT 'N'
ELSE
PRINT 'Y'
/*
Table 'Worktable'. Scan count 0, logical reads 0
Table 'spt_monitor'. Scan count 1, logical reads 1
*/
我相信您可以依赖大多数(如果不是全部)现代语言中 IF 语句的短路行为。您可以尝试通过首先放置一个真实条件并替换您的第二个条件来进行测试,1/0
如果没有发生短路,您可以将其除以零错误,如下所示:
IF 1>0 OR 1/0 BEGIN
PRINT 'Short Circuited'
END
如果你不相信这一点,你总是可以重写你的查询来做到这一点:
IF EXISTS(SELECT...) BEGIN
IF EXISTS(SELECT...) BEGIN
...
END
END
如果我用 AND 执行查询,即使那样,两个表都被访问
SET STATISTICS IO ON IF EXISTS (SELECT * from master..spt_values where [name] = 'rpcc') and EXISTS(SELECT * from master..spt_monitor where pack_sent = 5235252) PRINT 'Y'
表“spt_monitor”。扫描计数 1,逻辑读取 1,物理读取 0,预读读取 0,lob 逻辑读取 0,lob 物理读取 0,lob 预读读取 0。表“spt_values”。扫描计数 1,逻辑读取 17,物理读取 0,预读读取 0,lob 逻辑读取 0,lob 物理读取 0,lob 预读读取 0。
我从 sqlteam 上的以下博客条目中引用以下引号:
它会在你喜欢的时候出现,但不是你立即想到的方式。
作为开发人员,您必须知道SQL Server 不会像在其他编程语言中那样进行短路,并且您无法强制它执行.
有关更多详细信息,请查看上述博客条目中的第一个链接,该链接指向另一个博客:
最终判决?好吧,我还没有真正的,但可以肯定地说,唯一可以确保特定短路的情况是在 CASE 表达式中表达多个 WHEN 条件时。 使用标准布尔表达式,优化器将根据您正在查询的表、索引和数据移动它认为合适的东西。
有一个有趣的观察。我有两张桌子 tbla 和 tblb。tbla 有一个主键(idvalue),在 tblb 中用作外键。两者都有 idvalue = 1 的行,但没有 idvalue 为 -1 的行。现在,下面的查询只使用一张表
select 1
where exists
(select 1 from tbla where idvalue = -1)
and exists (select 1 from tblb where idvalue= 1)
给
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'tbla'. Scan count 0, logical reads 3, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
这很明显,因为优化器知道,由于存在主键-外键关系,因此如果 tbla 中缺少该值,则它永远不会出现在 tblb 中。因此,优化器将决定运行时不需要在 tblb 上查找。
但是,如果我将查询写为
select 1
where exists
(select 1 from tbla where idvalue = 1)
and exists (select 1 from tblb where idvalue= -1)
然后访问两个表。这是非常明显的,因为这里优化器知道它必须检查两个地方以确保满足 AND 条件。
但是,在这两种情况下,实际的执行计划都显示在 tbla 和 tblb 上进行搜索。这对我来说似乎很奇怪。对此有什么想法吗?
没有。
我刚刚在 SQL Server 2008 中进行了测试,如果第一次评估失败,它会立即跳过该IF
块。
这很容易测试。
对于您的第一次评估,请执行类似IF 1=0
的操作,对于您的第二次评估,请执行任何操作,然后显示实际的执行计划。在我的情况下,它只进行常量扫描来评估这些常量。
您可以通过执行以下操作来防止第二次扫描:
declare @test bit
select @test = case when exists(select 1...) then 1 else 0 end
if @test = 1
begin
--1st test passed
select @test = case when exists(select 2...) then 1 else 0 end
end
if @test = 1
begin
print 'both exists passed'
end