与嵌套选择混淆性能增益 * 不存在
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 应该给你你想要的以上是关于与嵌套选择混淆性能增益 * 不存在的主要内容,如果未能解决你的问题,请参考以下文章