4

我要查询的两个表各有约 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 中元素的内部 cat 的每个元素的集合

第二次更新

我看到我的数学与我的 SQL 不一致。

4

5 回答 5

3

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

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

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

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

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

于 2012-08-03T14:24:02.747 回答
2

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

Cats您将外部查询的表别名为outside,但您没有outsidewhere not exists. 因此,SQL 可以执行以下操作:

  • 找到任何一只猫在哪里cat.foo = dog.foo and cat.bar = dog.bar(从你最里面的查询)
  • 这意味着确实存在一只猫可以满足where not exists 所有猫的外在outside
  • 因此该where not exists子句false适用于所有行outside
  • 因此查询的结果为空

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

于 2012-08-03T14:28:48.580 回答
1

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

作为旁注,您应该尝试此等效查询(另请参阅https://stackoverflow.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

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

于 2012-08-03T14:22:45.107 回答
0

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

Select cat.foo,cat.bar from cats cat

MINUS

Select dog.foo,dog.bar from dogs dog

这是有效的,因为我的所有列都不能为空。

于 2012-08-07T12:58:17.010 回答
-1

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

   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
                      ) 
于 2012-08-03T14:17:24.993 回答