SQL“EXISTS”用法变体的性能

时间:2009-01-08 13:27:50

标签: sql sql-execution-plan

以下三个SQL语句的性能是否存在差异?

SELECT * FROM tableA WHERE EXISTS (SELECT * FROM tableB WHERE tableA.x = tableB.y)

SELECT * FROM tableA WHERE EXISTS (SELECT y FROM tableB WHERE tableA.x = tableB.y)

SELECT * FROM tableA WHERE EXISTS (SELECT 1 FROM tableB WHERE tableA.x = tableB.y)

它们都应该工作并返回相同的结果集。但是,如果内部SELECT选择tableB的所有字段,一个字段,或只是一个常量?

,这是否重要?

当所有陈述行为相同时,是否有最佳做法?

9 个答案:

答案 0 :(得分:32)

关于EXISTS子句的真相是SELECT子句不在EXISTS子句中计算 - 你可以尝试:

SELECT * 
  FROM tableA 
 WHERE EXISTS (SELECT 1/0 
                 FROM tableB 
                WHERE tableA.x = tableB.y)

...并且应该期望除以零错误,但是你不会因为它没有被评估。这就是为什么我的习惯是在EXISTS中指定NULL以证明可以忽略SELECT:

SELECT * 
  FROM tableA 
 WHERE EXISTS (SELECT NULL
                 FROM tableB 
                WHERE tableA.x = tableB.y)

EXISTS子句中最重要的是FROM和beyond子句 - WHERE,GROUP BY,HAVING等。

这个问题没有考虑到数据库的标记,应该是因为供应商以不同的方式处理事情 - 所以测试,并检查解释/执行计划以确认。版本之间的行为可能会发生变化......

答案 1 :(得分:15)

绝对是#1。它“看起来”可怕,但意识到优化器会做正确的事情,并表达意图。如果一个人意外地认为EXISTS但输入IN,那么这也是一个轻微的错误奖励。 #2是可以接受但不具有表现力。第三个选项在我不太谦虚的意见中发臭。它太接近于说“如果'没有价值'存在”舒适。

一般而言,如果代码提供其他好处并且实际上不会影响性能,则不要害怕编写看起来效率低下的代码。

也就是说,优化器几乎总是执行复杂的连接/选择/分组向导,以同样的方式保存简单的EXISTS /子查询。

在给自己kudos巧妙地重写那个令人讨厌的OR之后你会最终意识到优化器仍然使用相同的蹩脚执行计划来解决更容易理解的嵌入式OR查询。

故事的寓意是了解你的平台优化器。尝试不同的事情,看看实际上正在做什么,因为关于“装饰性”查询优化的猖獗的膝盖抽搐假设几乎总是不正确的,与我的经验无关。

答案 2 :(得分:6)

我意识到这是一篇旧帖子,但我认为重要的是要明确为什么可能选择一种格式而不是另一种格式。

首先,正如其他人所指出的那样,数据库引擎 假设 忽略Select子句。每个版本的SQL Server都有/确实,Oracle有,MySQL确实等等。在很多很多数据库开发中,我只遇到过一个没有正确忽略Select子句的DBMS:Microsoft Access。具体来说,旧版本的MS Access(我不能说当前版本)。

在我发现这个“功能”之前,我曾经使用Exists( Select *...。但是,我发现MS Access将流经子查询中的每一列,然后丢弃它们(Select 1/0也不起作用)。这说服我切换到Select 1。如果即使一个DBMS是愚蠢的,另一个也可能存在。

写作Exists( Select 1...在传达意图方面非常明确(坦率地说,“如果'没有价值'存在”这是非常接近于说“安慰。”并且使得DBMS做的几率很高一些愚蠢的Select语句几乎是不可能的。 Select Null可以达到同样的目的,但只需写更多字符。

我切换到Exists( Select 1以确保DBMS不会是愚蠢的。然而,那是很多以前的事情,今天我希望大多数开发人员都期望看到Exists( Select *完全相同。

尽管如此,即使您的DBMS正确评估它,我也可以提供一个避免Exists(Select *的正当理由。如果您不必在Exists子句中跳过其使用的每个实例,则更容易找到并破坏Select *的所有用法。

答案 3 :(得分:3)

至少在SQL Server中,

可以从磁盘读取的最小数据量是磁盘空间的单个“页面”。一旦处理器读取满足子查询谓词的一条记录就可以停止。子查询不会像它自己站在一样执行,然后包含在外部查询中,它作为整个查询计划的一部分执行。因此,当用作子查询时,Select子句中的内容并不重要,无论如何都不会向外部查询返回任何内容,除了指示是否找到单个记录的布尔值...

这三个人都使用完全相同的执行计划

我总是使用[Select * From ...]因为我认为它读起来更好,并不意味着我想从子查询返回特定的东西。

编辑:来自dave costa评论...... Oracle也对所有三个选项使用相同的执行计划

答案 4 :(得分:1)

EXISTS返回布尔而非实际数据,表示最佳做法是使用#3。

答案 5 :(得分:1)

这是开始某种圣战的那些问题之一。

有一个相当不错的讨论here

我认为答案可能是使用第三种选择,但速度提升是如此微不足道,这真的不值得担心。这很容易就是SQL Server可以在内部进行优化的那种查询,因此您可能会发现所有选项都是等效的。

答案 6 :(得分:0)

Execution Plan。 学习它,使用它,喜欢它

真的无法猜测。

答案 7 :(得分:0)

除了其他人所说的,使用SELECT 1的做法源自旧的Microsoft SQL Server(2005年之前) - 它的查询优化器不够聪明,无法避免从表中物理地获取字段{{ 1}}。据我所知,没有其他DBMS存在这种缺陷。

EXISTS测试行的存在,而不是它们中的内容,所以除了类似于上面的一些优化器怪癖之外,SELECT列表中的内容并不重要。

SELECT *似乎是最常见的,但其他人也可以接受。

答案 8 :(得分:-4)

#3应该是最好的,因为你无论如何都不需要返回的数据。带来这些字段只会增加额外的开销