联接与子查询


835

我是一个老派的MySQL用户,并且始终喜欢JOIN子查询。但是如今,每个人都使用子查询,而我讨厌它。我不知道为什么

我缺乏理论知识来自行判断是否存在差异。子查询是否与a一样好JOIN,因此不必担心吗?


23
子查询有时很棒。他们在MySQL中会影响性能。不要使用它们。
runrig 2010年

8
我总是给人一种印象,即在某些数据库技术中,子查询隐式地作为联接执行。
Kezzer 2010年

18
子查询并不总是很烂,当与相当大的表连接时,首选方法是从该大表中进行子选择(限制行数),然后再进行连接。
ovais.tariq,2010年

136
“如今每个人都在使用子查询” [需要引用]
Piskvor于

Answers:


190

取自MySQL手册13.2.10.11将子查询重写为Joins):

LEFT [OUTER] JOIN可以比同等子查询更快,因为服务器可能可以更好地对其进行优化-这并非仅针对MySQL Server。

因此子查询的速度可能比慢LEFT [OUTER] JOIN,但我认为子查询的强度是可读性稍高的。


45
@ user1735921取决于它的IMO ...通常,代码的可读性非常重要,因为它对于以后的管理非常重要...让我们记住Donald Knuth的著名说法:“过早的优化是所有人的根本。编程(或至少其中大部分)是邪恶的”。但是,自然地,在编程领域中,性能是最重要的……理想情况下,当一个人成功地将一个人与另一个人调和时:)
simhumileco

30
在更复杂的查询中,我发现联接比子查询更容易阅读。子查询变成了我头上一碗面条。
Zahra

6
@ user1735921当然可以,尤其是当查询变得如此复杂以至于它做错了事情并且您花费一天的时间进行修复时……与往常一样,两者之间保持着平衡。
fabio.sussetto

6
@ user1735921只有性能提升值得将来增加所需的维护时间
Joshua Schlichting

3
我的看法Joinsub query语法有所不同,因此可读性我们无法比拟,只要您精通SQL语法,两者的可读性就更高。性能更重要。
Thavaprakash Swaminathan

840

子查询是解决以下形式的问题的逻辑上正确的方法:“从A获取事实,以从B获取事实为条件”。在这种情况下,将B留在子查询中比执行联接更具逻辑意义。从实际意义上讲,它也是更安全的,因为您不必因与B的多次比赛而从A获得重复的事实时保持谨慎。

但是,实际上,答案通常取决于性能。一些优化器在进行联接与子查询时会吸柠檬,而另一些则以另一种方式吸柠檬,这是特定于优化器,特定于DBMS版本和特定于查询的。

从历史上看,显式联接通常会获胜,因此联接的既定常识是更好的,但优化器一直都在改进,因此我更喜欢先以逻辑上一致的方式编写查询,然后在性能约束允许的情况下进行重组。


105
好答案。我还要补充一点,开发人员(尤其是业余开发人员)并不总是精通SQL。
阿尔瓦罗·冈萨雷斯

4
+1正在寻找这个问题很长时间了一些合乎逻辑的解释,这仅仅是似乎是合乎逻辑我的答案
阿里Umair

1
@Marcelo Cantos,能否请您举例说明一下:“从实际意义上讲,它也更安全,因为由于与B的多次匹配,您不必谨慎地从A获取重复的事实。”?我发现这很有见地,但又有点抽象。谢谢。

6
@JinghuiNiu谁买贵重物品的顾客:select custid from cust join bought using (custid) where price > 500。如果客户购买了多件昂贵的商品,您将获得双倍优惠。要解决此问题,select custid from cust where exists (select * from bought where custid = cust.custid and price > 500)。您可以改用select distinct …它,但是对于优化器或评估器,通常要做更多的工作。
马塞洛·坎托斯

1
@MatTheWhale是的,我使用了过于简单的答案,因为我很懒。在实际情况下,您将不仅仅是从客户中获取客户。
马塞洛·坎托斯

357

在大多数情况下,JOINs比子查询快,而子查询也要快得多。

JOINRDBMS中,RDBMS可以创建一个更适合您的查询的执行计划,并且可以预测应加载哪些数据以进行处理并节省时间,这与子查询不同,子查询将运行所有查询并加载所有数据以进行处理。

子查询的优点是它们比JOINs 更易读:这就是为什么大多数新的SQL人士更喜欢它们的原因;这是简单的方法;但是就性能而言,JOINS在大多数情况下都更好,即使它们也不难阅读。


14
是的,因此大多数数据库都将其作为优化步骤,以在分析查询时将子查询转换为联接。
电影

16
对于提出的问题,此答案有点过于简化。如您所说:某些子查询可以,某些子查询不能。答案实际上并不能帮助区分两者。(“非常罕见”的确取决于您的数据/应用程序)。
不合理

21
您可以通过文档参考或测试结果来证明您的观点吗?
维吾尔族Gümüşhan

62
我对子查询有很好的体验,这些子查询包含对上层查询的反向引用,尤其是当行数大于100,000时。问题似乎是内存使用情况和分页到交换文件。联接将产生大量数据,这些数据可能不适合内存,必须分页到交换文件中。只要是这种情况,与联接select * from a where a.x = (select b.x form b where b.id = a.id)相比,小的子选择的查询时间就非常小。这是一个非常具体的问题,但是在某些情况下,它使您从几个小时转变为几分钟。
zuloo 2011年

13
我对Oracle很有经验,我可以说,如果您不对大型表进行任何筛选或排序,那么对大表进行子查询要好得多。
阿米尔·帕沙扎德

130

使用EXPLAIN可以查看数据库如何对数据执行查询。这个答案有一个很大的“取决于” ...

当PostgreSQL认为一个子查询比另一个查询快时,可以将它重写为一个联接或一个子查询的联接。这完全取决于数据,索引,相关性,数据量,查询等。


6
这就是为什么postgresql如此出色和有用的原因,它了解目标是什么,并会根据它认为更好的东西来修复查询,并且postgresql非常善于知道如何查看其数据
WojonsTech

。我想无需为我重新编写大量查询!Postgresql为赢。
Daniel Shin

77

在2010年,我会加入这个问题的作者的行列,并会投赞成票JOIN,但有了更多的经验(尤其是在MySQL中),我可以说:是的,子查询会更好。我在这里阅读了多个答案;一些规定的子查询速度更快,但是缺少很好的解释。我希望我可以提供这个(非常)较晚的答案:

首先,让我说最重要的:子查询有不同形式

第二个重要声明:大小很重要

如果使用子查询,则应了解 DB-Server如何执行子查询。特别是如果子查询被评估一次或每一行! 另一方面,现代的数据库服务器可以进行很多优化。在某些情况下,子查询有助于优化查询,但是较新版本的DB-Server可能会使优化过时。

选择字段中的子查询

SELECT moo, (SELECT roger FROM wilco WHERE moo = me) AS bar FROM foo

请注意,会针对的每个结果行执行子查询foo
尽可能避免这种情况;这可能会极大地降低您对庞大数据集的查询速度。但是,如果子查询没有引用,foo则DB服务器可以将其优化为静态内容,并且只能进行一次评估。

Where语句中的子查询

SELECT moo FROM foo WHERE bar = (SELECT roger FROM wilco WHERE moo = me)

如果幸运的话,数据库会在内部对其进行优化JOIN。如果不是这样,您的查询在庞大的数据集上将变得非常非常慢,因为它将对中的每一行执行子查询foo,而不仅是选择类型中的结果。

联接语句中的子查询

SELECT moo, bar 
  FROM foo 
    LEFT JOIN (
      SELECT MIN(bar), me FROM wilco GROUP BY me
    ) ON moo = me

这是有趣的。我们结合JOIN一个子查询。这就是子查询的真正优势。想象一个数据集中有几百万行,wilco但只有几个不同的行me。现在,我们不再需要针对一个巨大的表进行联接,而是需要一个较小的临时表进行联接。根据数据库的大小,这可能导致查询更快。与CREATE TEMPORARY TABLE ...和可以具有相同的效果INSERT INTO ... SELECT ...,这可以在非常复杂的查询中提供更好的可读性(但可以将数据集锁定在可重复的读取隔离级别)。

嵌套子查询

SELECT moo, bar
  FROM (
    SELECT moo, CONCAT(roger, wilco) AS bar
      FROM foo
      GROUP BY moo
      HAVING bar LIKE 'SpaceQ%'
  ) AS temp_foo
  ORDER BY bar

您可以将子查询嵌套在多个级别中。如果必须对结果进行分组或排序,这可以对庞大的数据集有所帮助。通常,DB-Server为此创建一个临时表,但是有时您不需要对整个表进行排序,而只需要对结果集进行排序。根据表的大小,这可能会提供更好的性能。

结论

子查询不能代替a JOIN,您不应该这样使用它们(尽管可以)。以我的拙见,正确使用子查询就是快速替换CREATE TEMPORARY TABLE ...。一个好的子查询会减少您无法在ON语句中完成的数据集JOIN。如果子查询具有关键字GROUP BY或之一,DISTINCT并且最好不在选择字段或where语句中,则它可能会大大提高性能。


3
对于Sub-queries in the Join-statement:(1)从子查询本身生成派生表可能需要很长时间。(2)结果派生表未建立索引。仅这两个因素就可能大大降低SQL的速度。
jxc

@jxc我只能代表MySQL(1)它有一个类似于联接的临时表。时间取决于数据量。如果无法使用子查询来减少数据,请使用联接。(2)是的,这取决于可以减少临时表中数据的因素。在现实世界中,我可以将联接大小从几百万个减少到几百个,并通过子查询将查询时间从几秒钟(使用完整索引)减少到四分之一秒。
Trendfischer

IMO:(1)此类临时表(派生表)未实现,因此,每次运行SQL时,都必须重新创建临时表,这可能会非常耗费成本,并且是一个真正的瓶颈(即,成千上万的人运行一个组) (2)即使您可以将临时表的大小减少到10记录中,因为没有索引,这仍然意味着在联接其他表时查询的数据记录比不包含临时表的查询多9倍。顺便说一句,我之前使用db(MySQL)遇到了这个问题,在我的情况下,使用子查询SELECT list可能会更快。
jxc

@jxc我毫不怀疑有很多例子,其中使用子查询不是最佳选择。最佳做法EXPLAIN是,在优化之前,应在查询上使用。set profiling=1如果使用临时表是瓶颈,那么使用旧表很容易看到。甚至索引也需要处理时间,B树可优化记录查询,但是10条记录表可能比索引数百万条记录的速度快得多。但这取决于多个因素,例如字段大小和类型。
Trendfischer

1
我真的很喜欢你的解释。谢谢。
unpairestgood

43

首先,要比较两者,您应该将带有子查询的查询区分开来:

  1. 一类子查询,这些子查询始终具有用联接编写的相应等效查询
  2. 一类无法使用联接重写的子查询

对于头等舱查询,一个好的RDBMS会将联接和子查询视为等效项,并将产生相同的查询计划。

这些天连mysql都可以做到。

尽管如此,有时还是不能,但这并不意味着联接将永远获胜-在mysql中使用子查询时,我的情况有所改善。(例如,如果某些因素阻止mysql计划者正确估计成本,并且如果计划者看不到join-variant和subquery-variant相同,则子查询可以通过强制某个路径来胜过联接)。

结论是,如果您想确保哪个查询的性能更好,则应该针对联接和子查询变量测试您的查询。

对于第二类,该比较没有意义,因为无法使用联接重写那些查询,在这种情况下,子查询是完成所需任务的自然方法,因此您不应歧视它们。


1
您能否提供一个使用子查询编写的查询示例,该子查询无法转换为联接(您称之为第二类)?
Zahra

24

我认为所引用答案中未充分强调的是特定(使用)情况下可能出现的重复和有问题的结果。

(尽管Marcelo Cantos确实提到了它)

我将引用斯坦福大学关于SQL的Lagunita课程中的示例。

学生桌

+------+--------+------+--------+
| sID  | sName  | GPA  | sizeHS |
+------+--------+------+--------+
|  123 | Amy    |  3.9 |   1000 |
|  234 | Bob    |  3.6 |   1500 |
|  345 | Craig  |  3.5 |    500 |
|  456 | Doris  |  3.9 |   1000 |
|  567 | Edward |  2.9 |   2000 |
|  678 | Fay    |  3.8 |    200 |
|  789 | Gary   |  3.4 |    800 |
|  987 | Helen  |  3.7 |    800 |
|  876 | Irene  |  3.9 |    400 |
|  765 | Jay    |  2.9 |   1500 |
|  654 | Amy    |  3.9 |   1000 |
|  543 | Craig  |  3.4 |   2000 |
+------+--------+------+--------+

申请表

(针对特定大学和专业的申请)

+------+----------+----------------+----------+
| sID  | cName    | major          | decision |
+------+----------+----------------+----------+
|  123 | Stanford | CS             | Y        |
|  123 | Stanford | EE             | N        |
|  123 | Berkeley | CS             | Y        |
|  123 | Cornell  | EE             | Y        |
|  234 | Berkeley | biology        | N        |
|  345 | MIT      | bioengineering | Y        |
|  345 | Cornell  | bioengineering | N        |
|  345 | Cornell  | CS             | Y        |
|  345 | Cornell  | EE             | N        |
|  678 | Stanford | history        | Y        |
|  987 | Stanford | CS             | Y        |
|  987 | Berkeley | CS             | Y        |
|  876 | Stanford | CS             | N        |
|  876 | MIT      | biology        | Y        |
|  876 | MIT      | marine biology | N        |
|  765 | Stanford | history        | Y        |
|  765 | Cornell  | history        | N        |
|  765 | Cornell  | psychology     | Y        |
|  543 | MIT      | CS             | N        |
+------+----------+----------------+----------+

让我们尝试找到已申请CS专业的学生的GPA分数(不考虑大学)

使用子查询:

select GPA from Student where sID in (select sID from Apply where major = 'CS');

+------+
| GPA  |
+------+
|  3.9 |
|  3.5 |
|  3.7 |
|  3.9 |
|  3.4 |
+------+

该结果集的平均值为:

select avg(GPA) from Student where sID in (select sID from Apply where major = 'CS');

+--------------------+
| avg(GPA)           |
+--------------------+
| 3.6800000000000006 |
+--------------------+

使用联接:

select GPA from Student, Apply where Student.sID = Apply.sID and Apply.major = 'CS';

+------+
| GPA  |
+------+
|  3.9 |
|  3.9 |
|  3.5 |
|  3.7 |
|  3.7 |
|  3.9 |
|  3.4 |
+------+

此结果集的平均值:

select avg(GPA) from Student, Apply where Student.sID = Apply.sID and Apply.major = 'CS';

+-------------------+
| avg(GPA)          |
+-------------------+
| 3.714285714285714 |
+-------------------+

显然,第二次尝试在我们的用例中会产生误导性的结果,因为它会计算重复次数以计算平均值。这也是明显的是使用distinct与连接-基于语句将消除问题,因为它会错误地保留三分之一的出现在的3.9成绩。鉴于我们实际上有两名(2)具有该分数的学生符合我们的查询条件,因此正确的情况是考虑该分数的两次出现(2)3.9

似乎在某些情况下,除了任何性能问题之外,子查询是最安全的方法。


我认为您不能在此处使用子查询。这不是逻辑上可以使用任何一种的情况,但是由于技术上的实现,它给出的答案是错误的。在这种情况下,您不能使用子查询,因为不属于CS的学生可以在IN分数列表中获得3.9的分数。一旦执行子查询,CS的上下文就会丢失,这在逻辑上并不是我们想要的。因此,这不是一个可以使用两者的好例子。对于此用例,子查询的使用在概念/逻辑上是错误的,即使幸运的是,它可以为其他数据集提供正确的结果。
萨拉·帕蒂(Saurabh Patil)'18

22

SQL Server的MSDN文档说

可以将许多包含子查询的Transact-SQL语句替换为联接。其他问题只能与子查询一起提出。在Transact-SQL中,包含子查询的语句与不包含子查询的语义等效版本之间通常没有性能差异。但是,在某些情况下必须检查是否存在,联接会产生更好的性能。否则,必须为外部查询的每个结果处理嵌套查询,以确保消除重复项。在这种情况下,联接方法将产生更好的结果。

所以如果你需要类似的东西

select * from t1 where exists select * from t2 where t2.parent=t1.id

尝试改用join。在其他情况下,它没有区别。

我说:创建函数为子查询消除了混乱的问题,并允许您对子查询实施其他逻辑。因此,我建议尽可能为子查询创建函数。

代码混乱是一个大问题,数十年来,业界一直致力于避免代码混乱。


9
在某些RDBMS(例如Oracle)中,用函数替换子查询是一个非常糟糕的主意,因此,我建议相反的做法-尽可能使用子查询/联接而不是函数。
Frank Schmitt

3
@FrankSchmitt,请通过引用支持您的论点。
维吾尔族Gümüşhan

2
在某些情况下,即使您检查是否存在,也应该使用子查询而不是联接:如果您检查NOT EXISTS。A 之所以NOT EXISTS胜出是LEFT OUTER JOIN因为各种原因:性能,故障安全性(在可空列的情况下)和可读性。sqlperformance.com/2012/12/t-sql-queries/left-anti-semi-join
Tim Schmelter 2013年

16

在旧的Mambo CMS的大型数据库上运行:

SELECT id, alias
FROM
  mos_categories
WHERE
  id IN (
    SELECT
      DISTINCT catid
    FROM mos_content
  );

0秒

SELECT
  DISTINCT mos_content.catid,
  mos_categories.alias
FROM
  mos_content, mos_categories
WHERE
  mos_content.catid = mos_categories.id;

〜3秒

解释表明,他们检查的行数完全相同,但其中一个需要3秒钟,而另一个几乎是即时的。故事的道德启示?如果性能很重要(不是什么时候?),请尝试多种方式,看看哪种方式最快。

和...

SELECT
  DISTINCT mos_categories.id,
  mos_categories.alias
FROM
  mos_content, mos_categories
WHERE
  mos_content.catid = mos_categories.id;

0秒

再次,相同的结果,检查了相同的行数。我的猜测是,找出DISTINCT mos_content.catid比发现DISTINCT mos_categories.id需要更长的时间。


1
我想更多地了解您要在最后一行中指出的内容“我的猜测是,与DISTINCT mos_categories.id相比,发现DISTINCT mos_content.catid需要更长的时间。” 。您是说一个ID应该仅被命名id,而不应被命名为catid?尝试优化我的数据库访问,您的学习会有所帮助。
bool.dev 2011年

2
在这种情况下,使用SQL IN是一种不好的做法,它并不能证明任何事情。
维吾尔族Gümüşhan

15

根据我对两种情况的观察,如果一个表的记录少于100,000条,则联接将快速运行。

但是,如果一个表有超过100,000条记录,那么最好使用子查询。

我在查询下面创建的一张表上有500,000条记录,其结果时间像

SELECT * 
FROM crv.workorder_details wd 
inner join  crv.workorder wr on wr.workorder_id = wd.workorder_id;

结果:13.3秒

select * 
from crv.workorder_details 
where workorder_id in (select workorder_id from crv.workorder)

结果:1.65秒


我同意,有时打破查询也行得通,当您有数百万条记录时,您就不希望使用联接,因为它们永远存在。宁可在代码中处理它,在代码中映射也更好。
user1735921

1
领带联接的工作速度不够快,您可能会缺少索引。查询分析器在比较实际性能时会非常有用。
digital.aaron

我同意阿贾伊·加耶拉(Ajay Gajera)的观点,我亲眼目睹了这一点。
user1735921

14
比较两个返回不同结果的查询的性能有何意义?
Paul Spiegel

是的,这些是不同的查询,但返回的结果相同
King neo

12

子查询通常用于返回单行作为原子值,尽管它们可以用于通过IN关键字将值与多行进行比较。在SQL语句中几乎所有有意义的点都允许使用它们,包括目标列表,WHERE子句等。一个简单的子查询可以用作搜索条件。例如,在一对表之间:

   SELECT title FROM books WHERE author_id = (SELECT id FROM authors WHERE last_name = 'Bar' AND first_name = 'Foo');

请注意,对子查询的结果使用普通值运算符要求仅返回一个字段。如果您有兴趣检查一组其他值中是否存在单个值,请使用IN:

   SELECT title FROM books WHERE author_id IN (SELECT id FROM authors WHERE last_name ~ '^[A-E]');

这显然与LEFT-JOIN不同,在LEFT-JOIN中,即使联接条件在表B中找不到任何匹配记录,您也只想从表A和B中联接东西,等等。

如果您只是担心速度,则必须检查数据库并编写良好的查询,看看性能是否有任何显着差异。


11

MySQL版本:5.5.28-0ubuntu0.12.04.2-log

我还给人一种印象,即JOIN总是比MySQL中的子查询好,但是EXPLAIN是一种更好的判断方法。这是一个子查询比JOIN更好的示例。

这是我的查询,其中包含3个子查询:

EXPLAIN SELECT vrl.list_id,vrl.ontology_id,vrl.position,l.name AS list_name, vrlih.position AS previous_position, vrl.moved_date 
FROM `vote-ranked-listory` vrl 
INNER JOIN lists l ON l.list_id = vrl.list_id 
INNER JOIN `vote-ranked-list-item-history` vrlih ON vrl.list_id = vrlih.list_id AND vrl.ontology_id=vrlih.ontology_id AND vrlih.type='PREVIOUS_POSITION' 
INNER JOIN list_burial_state lbs ON lbs.list_id = vrl.list_id AND lbs.burial_score < 0.5 
WHERE vrl.position <= 15 AND l.status='ACTIVE' AND l.is_public=1 AND vrl.ontology_id < 1000000000 
 AND (SELECT list_id FROM list_tag WHERE list_id=l.list_id AND tag_id=43) IS NULL 
 AND (SELECT list_id FROM list_tag WHERE list_id=l.list_id AND tag_id=55) IS NULL 
 AND (SELECT list_id FROM list_tag WHERE list_id=l.list_id AND tag_id=246403) IS NOT NULL 
ORDER BY vrl.moved_date DESC LIMIT 200;

说明显示:

+----+--------------------+----------+--------+-----------------------------------------------------+--------------+---------+-------------------------------------------------+------+--------------------------+
| id | select_type        | table    | type   | possible_keys                                       | key          | key_len | ref                                             | rows | Extra                    |
+----+--------------------+----------+--------+-----------------------------------------------------+--------------+---------+-------------------------------------------------+------+--------------------------+
|  1 | PRIMARY            | vrl      | index  | PRIMARY                                             | moved_date   | 8       | NULL                                            |  200 | Using where              |
|  1 | PRIMARY            | l        | eq_ref | PRIMARY,status,ispublic,idx_lookup,is_public_status | PRIMARY      | 4       | ranker.vrl.list_id                              |    1 | Using where              |
|  1 | PRIMARY            | vrlih    | eq_ref | PRIMARY                                             | PRIMARY      | 9       | ranker.vrl.list_id,ranker.vrl.ontology_id,const |    1 | Using where              |
|  1 | PRIMARY            | lbs      | eq_ref | PRIMARY,idx_list_burial_state,burial_score          | PRIMARY      | 4       | ranker.vrl.list_id                              |    1 | Using where              |
|  4 | DEPENDENT SUBQUERY | list_tag | ref    | list_tag_key,list_id,tag_id                         | list_tag_key | 9       | ranker.l.list_id,const                          |    1 | Using where; Using index |
|  3 | DEPENDENT SUBQUERY | list_tag | ref    | list_tag_key,list_id,tag_id                         | list_tag_key | 9       | ranker.l.list_id,const                          |    1 | Using where; Using index |
|  2 | DEPENDENT SUBQUERY | list_tag | ref    | list_tag_key,list_id,tag_id                         | list_tag_key | 9       | ranker.l.list_id,const                          |    1 | Using where; Using index |
+----+--------------------+----------+--------+-----------------------------------------------------+--------------+---------+-------------------------------------------------+------+--------------------------+

使用JOIN的相同查询是:

EXPLAIN SELECT vrl.list_id,vrl.ontology_id,vrl.position,l.name AS list_name, vrlih.position AS previous_position, vrl.moved_date 
FROM `vote-ranked-listory` vrl 
INNER JOIN lists l ON l.list_id = vrl.list_id 
INNER JOIN `vote-ranked-list-item-history` vrlih ON vrl.list_id = vrlih.list_id AND vrl.ontology_id=vrlih.ontology_id AND vrlih.type='PREVIOUS_POSITION' 
INNER JOIN list_burial_state lbs ON lbs.list_id = vrl.list_id AND lbs.burial_score < 0.5 
LEFT JOIN list_tag lt1 ON lt1.list_id = vrl.list_id AND lt1.tag_id = 43 
LEFT JOIN list_tag lt2 ON lt2.list_id = vrl.list_id AND lt2.tag_id = 55 
INNER JOIN list_tag lt3 ON lt3.list_id = vrl.list_id AND lt3.tag_id = 246403 
WHERE vrl.position <= 15 AND l.status='ACTIVE' AND l.is_public=1 AND vrl.ontology_id < 1000000000 
AND lt1.list_id IS NULL AND lt2.tag_id IS NULL 
ORDER BY vrl.moved_date DESC LIMIT 200;

输出为:

+----+-------------+-------+--------+-----------------------------------------------------+--------------+---------+---------------------------------------------+------+----------------------------------------------+
| id | select_type | table | type   | possible_keys                                       | key          | key_len | ref                                         | rows | Extra                                        |
+----+-------------+-------+--------+-----------------------------------------------------+--------------+---------+---------------------------------------------+------+----------------------------------------------+
|  1 | SIMPLE      | lt3   | ref    | list_tag_key,list_id,tag_id                         | tag_id       | 5       | const                                       | 2386 | Using where; Using temporary; Using filesort |
|  1 | SIMPLE      | l     | eq_ref | PRIMARY,status,ispublic,idx_lookup,is_public_status | PRIMARY      | 4       | ranker.lt3.list_id                          |    1 | Using where                                  |
|  1 | SIMPLE      | vrlih | ref    | PRIMARY                                             | PRIMARY      | 4       | ranker.lt3.list_id                          |  103 | Using where                                  |
|  1 | SIMPLE      | vrl   | ref    | PRIMARY                                             | PRIMARY      | 8       | ranker.lt3.list_id,ranker.vrlih.ontology_id |   65 | Using where                                  |
|  1 | SIMPLE      | lt1   | ref    | list_tag_key,list_id,tag_id                         | list_tag_key | 9       | ranker.lt3.list_id,const                    |    1 | Using where; Using index; Not exists         |
|  1 | SIMPLE      | lbs   | eq_ref | PRIMARY,idx_list_burial_state,burial_score          | PRIMARY      | 4       | ranker.vrl.list_id                          |    1 | Using where                                  |
|  1 | SIMPLE      | lt2   | ref    | list_tag_key,list_id,tag_id                         | list_tag_key | 9       | ranker.lt3.list_id,const                    |    1 | Using where; Using index                     |
+----+-------------+-------+--------+-----------------------------------------------------+--------------+---------+---------------------------------------------+------+----------------------------------------------+

rows列的比较说明了区别,并且使用JOIN的查询正在使用Using temporary; Using filesort

当然,当我运行两个查询时,第一个查询在0.02秒内完成,第二个查询即使在1分钟后仍未完成,因此EXPLAIN正确解释了这些查询。

如果我list_tag桌上没有INNER JOIN,即我移除了

AND (SELECT list_id FROM list_tag WHERE list_id=l.list_id AND tag_id=246403) IS NOT NULL  

从第一个查询开始,并相应地:

INNER JOIN list_tag lt3 ON lt3.list_id = vrl.list_id AND lt3.tag_id = 246403

从第二个查询中,然后EXPLAIN为两个查询返回相同数量的行,并且这两个查询的运行速度均相同。


我也有类似的情况,但是与您相比有更多的连接,将尝试解释一次
pahnin 2014年

在Oracle或PostgreSQL中,我会尝试过:不存在(在list_tag中选择1,list_id = l.list_id和tag_id在(43,55,246403)中)
David Aldridge

11

子查询具有即时计算聚合函数的能力。例如,找到这本书的最低价格,并获得以此价格出售的所有书籍。1)使用子查询:

SELECT titles, price
FROM Books, Orders
WHERE price = 
(SELECT MIN(price)
 FROM Orders) AND (Books.ID=Orders.ID);

2)使用JOIN

SELECT MIN(price)
     FROM Orders;
-----------------
2.99

SELECT titles, price
FROM Books b
INNER JOIN  Orders o
ON b.ID = o.ID
WHERE o.price = 2.99;


6
-1这是误导性的,因为您正在使用子查询并在两个示例中进行联接。由于数据库将执行完全相同的操作,因此已将子查询拉出到第二个查询以确定最低订单价格无效。另外,您不会使用子查询来重写联接;两个查询都使用联接。您正确的,子查询允许聚合函数,但是此示例并未证明这一事实。
David Harkness

我同意戴维的观点,您可以使用group by来获得最低价格。
user1735921

9
  • 一般规则是,在大多数情况下,连接速度更快(99%)。
  • 数据表越多,子查询就越慢。
  • 数据表的数量越少,子查询的速度与joins相等。
  • 子查询更简单,更容易理解,更容易阅读。
  • 大多数Web和应用程序框架及其“ ORM”和“活动记录”都会生成带有查询的查询,因为使用子查询更容易划分职责,维护代码等。
  • 对于较小的网站或应用程序,子查询是可以的,但是对于较大的网站和应用程序,您通常将不得不重写生成的查询以加入查询,特别是如果查询在查询中使用了许多查询。

有人说:“某些RDBMS 认为子查询比另一个查询快时,可以重写子查询联接联接子查询。”但是此语句适用于简单情况,当然不适用于带有子查询的复杂查询,而这些查询实际上会导致性能问题。


>但此语句适用于简单情况,据我所知,它可以是RDBMS可以重写为“ JOIN”的简单情况,也可以是子查询适合的复杂情况。:-)关于ORM的要点。我认为这具有最大的影响。
彼拉特

4

仅当第二个联接表具有比主表更多的数据时,才看到差异。我有以下经历...

我们有一个包含十万个条目的用户表,其会员数据(友谊)约为三十万个条目。为了获取朋友及其数据,这是一个join语句,但是延迟很大。但是在成员资格表中只有少量数据的情况下工作正常。一旦我们将其更改为使用子查询,它就可以正常工作。

但是与此同时,联接查询正在与条目少于主表的其他表一起使用。

因此,我认为联接和子查询语句运行良好,并且取决于数据和情况。


3

如今,许多数据库可以优化子查询和联接。因此,您只需要使用explain检查您的查询,然后看看哪一个更快。如果性能没有太大差异,我更喜欢使用子查询,因为它们简单易懂。


1

我只是在考虑相同的问题,但是我在FROM部分使用了子查询。我需要从大表进行连接和查询,“从”表有2800万条记录,但结果只有128个,所以小结果大数据!我在上面使用MAX()函数。

首先,我使用LEFT JOIN是因为我认为这是正确的方法,MySQL可以优化等。第二次只是为了测试,我针对JOIN重写了子选择。

LEFT JOIN运行时:1.12s SUB-SELECT运行时:0.06s

子选择比联接快18倍!就在副词adv。子选择看起来很糟糕,但结果是...


-1

如果要使用join加快查询速度:

对于“内部联接/联接”,请不要使用where条件,而应在“ ON”条件下使用它。例如:

     select id,name from table1 a  
   join table2 b on a.name=b.name
   where id='123'

 Try,

    select id,name from table1 a  
   join table2 b on a.name=b.name and a.id='123'

对于“左/右联接”,不要在“ ON”条件下使用,因为如果使用左/右联接,它将获得任何一张表的所有行。因此,请勿在“ On”中使用它。因此,尝试使用“在哪里”条件


这取决于SQL Server和查询的复杂性。许多SQL实现都会优化像这样的简单查询以获得最佳性能。也许提供一个示例服务器名称和版本来解决此问题?
Trendfischer
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.