25

下面三个 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 的所有字段、一个字段或只是一个常量,这有关系吗?

当所有语句表现相同时,是否有任何最佳实践?

4

9 回答 9

34

关于 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 等。

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

于 2010-11-06T21:51:57.343 回答
15

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

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

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

在为巧妙地从联接中重写讨厌的 OR 获得荣誉之后,您最终会意识到优化器仍然使用相同的糟糕执行计划来解决更容易理解的嵌入式 OR 查询。

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

于 2009-01-08T14:46:14.207 回答
7

我意识到这是一篇旧文章,但我认为重要的是要清楚说明为什么人们可能会选择一种格式而不是另一种格式。

首先,正如其他人指出的那样,数据库引擎应该忽略 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 *哪个会完全一样。

也就是说,Exists(Select *即使您的 DBMS 正确评估它,我也可以提供一个很好的理由来避免它。Select *如果您不必在 Exists 子句中跳过其使用的每个实例,则更容易找到并击败所有使用。

于 2012-06-14T07:15:24.810 回答
3

至少在 SQL Server 中,

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

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

我总是使用 [Select * From ... ] 因为我认为它读起来更好,而不是暗示我想要从子查询中特别返回一些东西。

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

于 2009-01-08T14:03:47.240 回答
1

EXISTS返回一个布尔值而不是实际数据,最好的做法是使用#3。

于 2009-01-08T13:44:35.687 回答
1

这是即将引发某种圣战的问题之一。

这里有一个相当好的讨论。

我认为答案可能是使用第三个选项,但速度提升是如此的无限小,真的不值得担心。它很容易成为 SQL Server 无论如何都可以在内部优化的查询类型,因此您可能会发现所有选项都是等效的。

于 2009-01-08T13:53:52.113 回答
0

执行计划。学习它,使用它,爱它

没有办法猜测,真的。

于 2009-01-08T14:52:16.753 回答
0

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

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

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

于 2012-06-16T00:09:27.397 回答
-4

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

于 2009-01-08T13:38:34.623 回答