【问题标题】:Join vs. sub-query加入与子查询
【发布时间】:2011-02-04 08:26:35
【问题描述】:

我是一个老派的 MySQL 用户,并且一直更喜欢 JOIN 而不是子查询。但是现在每个人都使用子查询,我讨厌它;我不知道为什么。

我缺乏自己判断是否有任何差异的理论知识。子查询是否与JOIN 一样好,因此无需担心?

【问题讨论】:

  • 子查询有时很棒。他们在 MySQL 中表现不佳。不要使用它们。
  • 我一直认为子查询在某些数据库技术中被隐式执行为连接。
  • 子查询并不总是很糟糕,当加入相当大的表时,首选的方法是从那个大表中进行子选择(限制行数)然后加入。跨度>
  • “现在每个人都使用子查询”[需要引用]

标签: sql mysql subquery join


【解决方案1】:

子查询是解决“从 A 获取事实,以来自 B 的事实为条件”形式的问题的逻辑正确方法。在这种情况下,将 B 粘贴在子查询中比进行联接更符合逻辑。从实际意义上讲,它也更安全,因为您不必因为与 B 的多次匹配而从 A 获取重复的事实。

然而,实际上,答案通常归结为性能。一些优化器在给定连接与子查询时会吃柠檬,而另一些则在另一种方式下吃柠檬,这是特定于优化器、特定于 DBMS 版本和特定于查询的。

从历史上看,显式连接通常会胜出,因此公认的连接更好,但优化器一直在变得更好,所以我更喜欢先以逻辑连贯的方式编写查询,然后在性能限制允许的情况下进行重组.

【讨论】:

  • 很好的答案。我还要补充一点,开发人员(尤其是业余的)并不总是精通 SQL。
  • +1 为这个问题找了很长时间的逻辑解释,这只是我觉得合乎逻辑的答案
  • @Marcelo Cantos,您能否举一个您的陈述示例“从实际意义上讲,这也更安全,因为您不必谨慎从 A 获取重复的事实,因为多个与 B 比赛。”?我发现这很有见地,但有点太抽象了。谢谢。
  • @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 …,但对于优化器或评估器来说,它通常需要更多的工作。
  • @MatTheWhale 是的,我使用了一个过于简单的答案,因为我很懒。在实际场景中,您会从 cust 中提取更多列,而不仅仅是 custid。
【解决方案2】:

子查询通常用于将单行作为原子值返回,尽管它们可用于使用 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,您只想连接表 A 和 B 中的内容,即使连接条件在表 B 中找不到任何匹配的记录,等等。

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

【讨论】:

    【解决方案3】:

    在大多数情况下,JOINs 比子查询更快,而且子查询更快的情况很少见。

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

    子查询的好处是它们比JOINs 更具可读性:这就是大多数新SQL 人喜欢它们的原因;这是简单的方法;但是在性能方面,JOINS 在大多数情况下会更好,即使它们也不难阅读。

    【讨论】:

    • 是的,因此大多数数据库都将其作为优化步骤包括在分析查询时将子查询转换为连接。
    • 这个答案对于所提出的问题来说有点过于简单了。正如您所说:某些子查询可以,某些子查询不行。答案并不能真正帮助区分两者。 (“非常罕见”也取决于您的数据/应用程序)。
    • 你能用文档参考或测试结果证明你的任何观点吗?
    • 我在包含对上层查询的反向引用的子查询方面取得了非常好的经验,尤其是在行数超过 100,000 时。事情似乎是内存使用和对交换文件的分页。连接会产生大量数据,这些数据可能不适合内存,必须分页到交换文件中。在这种情况下,像select * from a where a.x = (select b.x form b where b.id = a.id) 这样的小型子选择的查询时间与连接相比非常小。这是一个非常具体的问题,但在某些情况下,它会使您从数小时缩短到数分钟。
    • 我有使用 Oracle 的经验,我可以说,如果您没有对它们进行任何过滤或排序,子查询在大表上会好得多。
    【解决方案4】:

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

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

    【讨论】:

    • 这正是 postgresql 如此优秀和有用的原因,它了解目标是什么,并会根据它认为更好的方式修复查询,并且 postgresql 非常擅长了解如何查看其数据
    • 哇。我想不需要为我重写大量的查询!为胜利而战。
    • 我的 left joinsub query 慢得多,所以我认为它不会真正完成这项工作。
    【解决方案5】:

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

    1. 一类子查询,总是有相应的用连接编写的等效查询
    2. 一类不能使用连接重写的子查询

    对于第一类查询,良好的 RDBMS 会将连接和子查询视为等效,并生成相同的查询计划。

    现在连 mysql 都这样做了。

    尽管如此,有时它不会,但这并不意味着连接总是会获胜 - 我曾在 mysql 中使用子查询提高了性能。 (例如,如果有什么东西阻止 mysql planner 正确估计成本,并且如果 planner 没有看到 join-variant 和 subquery-variant 相同,那么子查询可以通过强制某个路径来胜过连接)。

    结论是,如果您想确定哪一种性能更好,您应该测试联接和子查询变体的查询。

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

    【讨论】:

    • 您能否提供一个使用子查询编写的查询示例,该查询无法转换为联接(您称之为第二类)?
    【解决方案6】:

    在来自旧 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 需要更长的时间才能弄清楚。

    【讨论】:

    • 我想更多地了解您在最后一行中试图指出的内容“我的猜测是 DISTINCT mos_content.catid 比 DISTINCT mos_categories.id 需要更长的时间才能弄清楚。” .你是说一个 id 应该只命名为 id 而不是命名为 catid ?尝试优化我的数据库访问,您的学习可能会有所帮助。
    • 在这种情况下使用 SQL IN 是一种不好的做法,它并不能证明任何事情。
    【解决方案7】:

    MSDN Documentation for SQL Server says

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

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

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

    尝试改用连接。在其他情况下,这没有什么区别。

    我说:为子查询创建函数可以消除混乱的问题,并允许您为子查询实现额外的逻辑。所以我建议尽可能为子查询创建函数。

    代码混乱是一个大问题,业界几十年来一直在努力避免它。

    【讨论】:

    • @FrankSchmitt 请用参考文献支持你的论点。
    • 在某些情况下,即使您检查是否存在,您也应该使用子查询而不是连接:如果您检查NOT EXISTSNOT EXISTS 胜过 LEFT OUTER JOIN 的原因有很多:性能、故障安全(在可空列的情况下)和可读性。 sqlperformance.com/2012/12/t-sql-queries/left-anti-semi-join
    【解决方案8】:

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

    【讨论】:

      【解决方案9】:

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

      我也有这样的印象,在 MySQL 中,JOIN 总是比子查询好,但 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 为两个查询返回相同数量的行,并且这两个查询运行速度同样快。

      【讨论】:

      • 我也有类似的情况,但是加入的次数比你多,会尝试解释一次
      • 在 Oracle 或 PostgreSQL 中我会尝试: AND NOT EXISTS (SELECT 1 FROM list_tag WHERE list_id=l.list_id AND tag_id in (43, 55, 246403))
      【解决方案10】:

      只有当第二个连接表的数据明显多于主表时,才能看到差异。我有过如下经历...

      我们有一个包含 10 万个条目的用户表,他们的会员数据(友谊)大约有 30 万个条目。这是为了获取朋友和他们的数据而加入的声明,但延迟很大。但它在成员表中只有少量数据的情况下工作正常。一旦我们将其更改为使用子查询,它就可以正常工作了。

      但与此同时,连接查询正在处理条目少于主表的其他表。

      所以我认为连接和子查询语句工作正常,这取决于数据和情况。

      【讨论】:

        【解决方案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;
        

        【讨论】:

        【解决方案12】:

        取自 MySQL 手册 (13.2.10.11 Rewriting Subqueries as Joins):

        LEFT [OUTER] JOIN 可以比等效的子查询更快,因为服务器可能能够更好地优化它——这一事实并非仅针对 MySQL 服务器。

        因此子查询可能比LEFT [OUTER] JOIN 慢,但在我看来,它们的优势在于可读性略高。

        【讨论】:

        • @user1735921 IMO 这取决于...一般来说,代码的可读性非常重要,因为它对于以后的管理非常重要...让我们记住 Donald Knuth 的著名声明: “过早的优化是编程中万恶之源(或至少是大部分)”。然而,自然有一些编程领域的性能是最重要的......理想情况下,当一个人成功地与另一个人协调时:)
        • 在更复杂的查询中,我发现连接比子查询更容易阅读。子查询在我脑海中变成了一碗面条。
        • @user1735921 当然,特别是当查询变得如此复杂以至于它做错了事情并且你花了一天时间来修复它时......像往常一样,两者之间有一个平衡。
        • @user1735921 前提是性能提升值得在未来增加维护时间
        • 我认为Joinsub query语法不同,可读性我们无法比较,只要你精通SQL语法,可读性都比较高。性能更重要。
        【解决方案13】:

        我认为在引用的答案中没有强调的是重复问题以及可能由特定(使用)案例产生的有问题的结果。

        (尽管马塞洛·坎托斯确实提到过)

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

        学生桌

        +------+--------+------+--------+
        | 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 分数的三分之一。正确的情况是考虑 3.9 分数的 两 (2) 次出现,因为我们实际上有 两 (2) 个学生的分数符合我们的查询标准。

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

        【讨论】:

        • 我认为你不能在这里使用子查询。这不是您在逻辑上可以使用任何一种的情况,但是由于它的技术实现而给出了错误的答案。在这种情况下,您不能使用子查询,因为不属于 CS 的学生可以在 IN 分数列表中获得 3.9 分。一旦执行子查询,CS 的上下文就会丢失,这在逻辑上不是我们想要的。所以这不是一个可以使用任何一个的好例子。对于这个用例,子查询的使用在概念上/逻辑上是错误的,即使幸运的是它为不同的数据集提供了正确的结果。
        【解决方案14】:

        根据我对两种情况的观察,如果一个表的记录少于 100,000 条,那么连接将很快工作。

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

        我有一张表,上面有 500,000 条记录,我在下面的查询中创建,它的结果时间是这样的

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

        结果:1​​3.3 秒

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

        结果:1​​.65 秒

        【讨论】:

        • 我同意,有时打破查询也有效,当你有数百万条记录时,你不想使用连接,因为它们永远存在。而是在代码中处理,在代码中映射比较好。
        • Tie 您的联接工作不够快,您可能缺少索引。查询分析器在比较实际性能方面非常有用。
        • 比较返回不同结果的两个查询的性能有何意义?
        • 是的,这些是不同的查询,但返回相同的结果
        • @anikislamshojib 仔细看看:我们在第一个语句中有 * 和两个表,但在第二个语句中只有一个表,所以 I 预计会有所不同列数。
        【解决方案15】:

        在 2010 年,我会加入这个问题的作者,并强烈投票给 JOIN,但有了更多的经验(尤其是在 MySQL 方面),我可以说:是的,子查询可以更好。我在这里阅读了多个答案;一些声明的子查询更快,但它缺乏一个很好的解释。我希望我能提供这个(非常)迟到的答案:

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

        第二个重要声明:尺寸很重要

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

        选择字段中的子查询

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

        请注意,对于来自foo 的每个结果行都会执行一个子查询。
        尽可能避免这种情况;它可能会大大减慢您对大型数据集的查询。但是,如果子查询没有引用foo,它可以由 DB-server 优化为静态内容,并且只能评估一次。

        Where 语句中的子查询

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

        如果幸运的话,数据库会在内部将其优化为 JOIN。否则,您的查询在大型数据集上将变得非常非常慢,因为它将为 foo 中的每一行执行子查询,而不仅仅是像 select-type 中的结果。

        Join 语句中的子查询

        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 VARIANCE(moo)
          FROM (
            SELECT moo, CONCAT(roger, wilco) AS bar
              FROM foo
              HAVING bar LIKE 'SpaceQ%'
          ) AS temp_foo
          GROUP BY moo
        

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

        结论

        子查询不能替代JOIN,您不应该像这样使用它们(尽管可能)。在我的拙见中,子查询的正确使用是作为CREATE TEMPORARY TABLE ... 的快速替换使用。一个好的子查询以您无法在 JOINON 语句中完成的方式减少数据集。如果子查询具有关键字 GROUP BYDISTINCT 之一,并且最好不位于 select 字段或 where 语句中,那么它可能会大大提高性能。

        【讨论】:

        • 对于Sub-queries in the Join-statement:(1)从子查询本身生成派生表可能需要很长时间。 (2) 生成的派生表没有索引。这两个单独可以显着减慢 SQL。
        • @jxc 我只能代表 MySQL (1) 那里有一个类似于连接的临时表。时间取决于数据量。如果您不能使用子查询减少数据,请使用联接。 (2) 这是对的,这取决于你可以减少临时表中数据的因素。我有真实的案例,我可以将连接大小从几百万减少到几百,并使用子查询将查询时间从几秒(使用完整索引)减少到四分之一秒。
        • IMO:(1)这样的临时表(派生表)没有具体化,因此每次运行 SQL 时,都必须重新创建临时表,这可能会非常昂贵并且是一个真正的瓶颈(即在数百万条记录上运行分组)(2)即使您可以将临时表的大小减少到10 记录,因为没有索引,这仍然意味着可能查询比 w/ 多 9 倍的数据记录o 加入其他表时的临时表。顺便说一句,我的 db(MySQL) 之前遇到过这个问题,就我而言,在 SELECT list 中使用子查询可能会快得多。
        • @jxc 我不怀疑有很多示例,其中使用子查询不太理想。作为一种好的做法,您应该在优化之前对查询使用EXPLAIN。使用旧的set profiling=1,您可以很容易地看到临时表是否是瓶颈。甚至一个索引也需要处理时间,B-Trees 优化了对记录的查询,但是一个 10 记录的表可以比数百万记录的索引快得多。但这取决于多种因素,例如字段大小和类型。
        • 使用EXPLAIN 应该足以了解 DERIVED 表(从 FROM 列表中的子查询创建)如何影响查询。我在工作中经常使用子查询,只是试着提一下sub-queries in the Join-statement 可能没有你想象的那么有前途。临时表中减少的记录的成本可能比其收益大得多。还要记住,即使最终的 JOIN 可能花费更少的时间,扫描子查询中数百万条记录的时间仍然存在,并且需要为每次 SQL 运行计算。
        【解决方案16】:

        我只是在考虑同样的问题,但我在 FROM 部分中使用了子查询。 我需要从大表中连接和查询,“从”表有 2800 万条记录,但结果只有 128 条这么小的结果大数据!我正在使用 MAX() 函数。

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

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

        子选择比连接快 18 倍!就在 chokito adv 中。子选择看起来很糟糕,但结果......

        【讨论】:

          【解决方案17】:
          • 一般规则是连接在大多数情况下更快 (99%)。
          • 数据表越多,子查询越慢。
          • 数据表越少,子查询的速度与joins相当。
          • 子查询更简单、更易于理解和阅读。
          • 大多数网络和应用程序框架及其“ORM”和“活动记录”使用子查询生成查询,因为使用子查询更容易分担责任,维护代码等
          • 对于较小的网站或应用程序,子查询是可以的,但对于较大的网站和应用程序,您通常需要重写生成的查询以 join 查询,特别是如果查询在查询中使用许多子查询

          有人说“某些 RDBMS 可以将 子查询 重写为 join 或将 join 重写为 子查询当它认为一个比另一个快时。”,但是这个陈述适用于简单的情况,肯定不适用于具有子查询的复杂查询,这实际上会导致性能问题。

          【讨论】:

          • > 但是这个语句适用于简单的情况,我知道这要么是一个简单的情况,可以被 RDBMS 重写为“JOIN”,要么是一个非常复杂的情况,子查询在这里是合适的。 :-) 关于 ORM 的好点。我认为这会产生最大的影响。
          【解决方案18】:

          如果您想使用连接加快查询速度:

          对于“内连接/连接”, 不要使用 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”条件下不要使用它。所以,尝试使用“Where”条件

          【讨论】:

          • 这取决于 SQL 服务器和查询的复杂性。许多 SQL 实现会优化这样的简单查询以获得最佳性能。也许提供一个示例服务器名称和版本,这种行为恰好可以改善答案?
          【解决方案19】:

          这取决于几个因素,包括您正在运行的特定查询、数据库中的数据量。子查询首先运行内部查询,然后再次从结果集中过滤出实际结果。而在 join 中运行并一次产生结果。

          最好的策略是你应该同时测试连接解决方​​案和子查询解决方案以获得优化的解决方案。

          【讨论】:

            【解决方案20】:

            我不是关系数据库专家,所以对此持保留态度。

            关于子查询与连接的一般概念是评估较大查询所采用的路径。

            为了执行更大的查询,必须先执行每个单独的子查询,然后将结果集存储为与更大查询交互的临时表。

            这个临时表没有索引,所以,任何比较都需要扫描整个结果集。

            相比之下,当您使用连接时,所有索引都在使用中,因此比较需要遍历索引树(或哈希表),这在速度方面成本要低得多。

            现在,我不知道最流行的关系引擎的较新版本是否会反向执行评估,并将必要的元素加载到临时表中,作为一种优化方法。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2012-04-27
              • 1970-01-01
              相关资源
              最近更新 更多