与嵌套选择混淆性能增益 * 不存在

Posted

技术标签:

【中文标题】与嵌套选择混淆性能增益 * 不存在【英文标题】:confusing performance gain with nested select * not exists 【发布时间】:2012-08-01 14:22:09 【问题描述】:

我查询的两个表各有约 1.5 亿行。

下面的语句我45分钟没有返回就终止了,所以不知道会运行多久:

select * from Cats cat  
where not exists( select dog.foo,dog.bar from Dogs dog
                  where cat.foo = dog.foo   
                  and cat.bar = dog.bar);

但是这个查询在大约 3 分钟内执行:

select * from Cats outside  
   where not exists(select * from Cats cat  
                     where exists( select dog.foo,dog.bar from Dogs dog
                      where cat.foo = dog.foo   
                      and cat.bar = dog.bar)));

我的问题是我看到这种性能提升的幕后发生了什么?

返回相同结果集的原因:

第一个查询(慢)状态给出基于 Cats 表不存在的所有元素。

第二个查询(快速)表示从确实存在的 Cats 子集中给出所有不存在的元素。

我希望得到以下查询:

select dog.foo,dog.bar from Dogs dog
                          where cat.foo = dog.foo   
                          and cat.bar = dog.bar  

返回 [A,B,C]

这对两个函数都是通用的。

我的猫桌有以下内容:[A,B,C,D,E]

我希望得到以下查询:

 select * from Cats cat  
                     where exists

返回 [A,B,C] 最后一段:

select * from Cats outside  
       where not exists

返回 [D,E]

更新

设置符号以在数学上证明我的主张(如果我使用了错误的符号,请纠正我):

∀ Cat (Ǝ cat ≠ Ǝdog)    

对于 Cat 中的所有元素,返回包含 cat 中不等于 dog 中的元素的每个元素的集合

∀ Cat (Ǝ cat = Ǝdog)   

对于 Cat 中的所有元素,返回包含 cat 中与 dog 中的元素相等的每个元素的集合

∀ Cat (Ǝ innerCat ≠ Ǝcat)  

对于Cat中的所有元素,返回包含不等于cat中元素的inner cat的每个元素的集合

第二次更新

我发现我的数学与我的 SQL 不一致。

【问题讨论】:

您是否尝试过 EXPLAIN 来查看计划? @podiluska 当我执行解释计划时,它不会产生任何东西。当我运行 select 语句来验证计划时。 它们确实返回了不同的结果集,这可以解释性能提升...... @podiluska 嗯,他们都应该返回相同的结果集。 @podiluska 是的,正确,我的数学与我的 SQL 不一致:( 【参考方案1】:

显然,NOT IN 和 NOT EXISTS 是数据引擎优化的问题。从技术上讲,这些被称为反连接(区别于等连接、半连接、非等连接等)。

当一个连接难以优化时,引擎会使用嵌套循环连接。这些通常是性能最差的类型(尽管在 SQL Server 执行计划中,这些通常看起来相同,因为 SQL Server 在执行计划中调用索引查找“嵌套循环”)。

这两个查询有什么区别?第一个只有一个 NOT EXISTS,所以它可能在做一些低效的事情。第二个是在最里面的子查询上执行 EXISTS。这首先得到优化,基本上作为一个连接。如果键有索引一切都很好。 SQL Server 还可以为这些选择基于散列或基于合并的算法。

第二个版本中的“不存在”是基于同一张表的。这可能会给 SQL Server 更多的优化空间。

最后,第二个版本可能会大大减少数据集。如果是这样,即使是外部的嵌套循环连接也可能会更快。

【讨论】:

【参考方案2】:

第二个查询在执行时更加优化,这就是为什么:

您将外部查询的Cats 表别名为outside,但您没有在where not exists 的子句中引用outside。因此,SQL 可以执行以下操作:

找到cat.foo = dog.foo and cat.bar = dog.bar(来自最里面的查询)的任何一只猫 这意味着确实存在一只猫满足你的外部where not exists对于outside中的所有猫 因此where not exists 子句对于outside 中的所有行都是false 因此查询结果为空

您的第一个查询必须为表中的每只猫重新执行嵌套查询,因此速度较慢。

【讨论】:

我看到我的问题是我的数学(如上所述)与我的查询不完全匹配。这有助于我了解自己做错了什么。 有没有办法避免重新执行内部查询?如果我最多只能进行一次该查询,那将是有益的。【参考方案3】:

您的问题的答案是检查执行计划。

作为旁注,您应该尝试这个等效查询(另见https://***.com/a/1069467/44522):

SELECT * FROM Cats cat LEFT OUTER JOIN Dogs dog 
    ON cat.foo = dog.foo and cat.bar = dog.bar
WHERE dog.foo IS NULL and dog.bar IS NULL

我打赌它会执行得更快(假设你有正确的索引)。

【讨论】:

你需要(并且 dog.bar 为空)吗? 是的。如果您没有明确排除它,您可能有 dog.foo = NULL 和 dog.bar = 'xyz',它们将在结果集中。承认,如果您通过主键/外键加入,这是一种非常不可能的情况。【参考方案4】:

我通过测试发现这是执行初始问题中查询的最有效方式:

Select cat.foo,cat.bar from cats cat

MINUS

Select dog.foo,dog.bar from dogs dog

这行得通,因为我的所有列都不能为空。

【讨论】:

【参考方案5】:

它们是具有不同结果的不同查询。要使第二个返回与第一个相同,它需要类似于...

   select * from cats outside   
   where not exists(select * from Cats cat   
                     where exists( select dog.foo,dog.bar from Dogs dog 
                      where cat.foo = dog.foo    
                      and cat.bar = dog.bar)
                      and outside.foo = cat.foo
                      and outside.bar=cat.bar
                      ) 

【讨论】:

第二个查询检查 dog 表中的任何匹配项 - 因此要么返回所有猫,要么不返回。第一个检查存在的那些。顺便说一句 - @MicSim 的 LEFT JOIN 应该给你你想要的

以上是关于与嵌套选择混淆性能增益 * 不存在的主要内容,如果未能解决你的问题,请参考以下文章

当存在 NULL 时,性能选择与另一个表中的条目不匹配的行

关于 Pusher 中存在通道与非存在通道关联的混淆

使用“不存在”与空关系测试混淆

HBT设计中存在问题

HBT设计中存在问题

存在/不存在:“选择 1”与“选择字段”