【问题标题】:JOIN queries vs multiple queriesJOIN 查询与多个查询
【发布时间】:2010-11-07 05:03:09
【问题描述】:

JOIN 查询是否比多个查询快? (您运行主查询,然后根据主查询的结果运行许多其他 SELECT)

我之所以问是因为加入它们会使我的应用程序的设计复杂化很多

如果它们更快,任何人都可以粗略地估计多少?如果是 1.5 倍,我不在乎,但如果是 10 倍,我想我会的。

【问题讨论】:

  • 我认为他们会更快。我知道一个 INSERT 与说 10 个单独的 INSERT 查询相比要快得多。
  • 您的多个查询是否在存储过程中以及它们是否来自应用程序可能很重要(使用此信息编辑您的问题)。前者会比后者快得多。
  • 我有一个问题,即连接比单个查询所需的时间总和慢得多,尽管有主键。我猜 SQLITE 试图通过遍历行并检查查询值而不是多次执行查询来节省时间。但是,如果您在该特定列上有一个快速索引,这在许多情况下效果不佳。

标签: mysql database join query-optimization


【解决方案1】:

这个问题很老,但缺少一些基准。我将 JOIN 与它的 2 个竞争对手进行了基准测试:

  • N+1 个查询
  • 2 个查询,第二个使用 WHERE IN(...) 或等效项

结果很清楚:在 MySQL 上,JOIN快得多。 N+1 次查询会大幅降低应用程序的性能:

也就是说,除非您选择大量指向极少数不同的外来记录的记录。这是极端情况的基准:

这在典型应用程序中不太可能发生,除非您要加入一对多关系,在这种情况下,外键位于另一个表上,并且您要多次复制主表数据。

外卖:

  • 对于 * 对一的关系,请始终使用 JOIN
  • 对于 * 对多的关系,第二个查询可能会更快

更多信息请参见my article on Medium

【讨论】:

    【解决方案2】:

    是否应该使用连接首先是关于连接是否有意义。只有在这一点上才需要考虑性能,因为几乎所有其他情况都会导致性能显着更差

    性能差异很大程度上取决于您查询的信息的相关程度。连接工作,当数据相关并且您正确索引内容时它们会很快,但它们通常会导致一些冗余,有时会导致超出需要的结果。如果您的数据集不直接相关,将它们放在单个查询中将产生所谓的笛卡尔积(基本上是所有可能的行组合),这几乎不是您想要的。

    这通常是由多对一对多关系引起的。例如,HoldOffHunger's answer 提到了对帖子、标签和 cmets 的单个查询。评论与帖子相关,标签也是如此……但标签与 cmets 无关。

    +------------+     +---------+     +---------+
    |  comment   |     |   post  |     |  tag    |
    |------------|*   1|---------|1   *|---------|
    | post_id    |-----| post_id |-----| post_id |
    | comment_id |     | ...     |     | tag_id  |
    | user_id    |     |         |     | ...     |
    | ...        |     |         |     | ...     |
    +------------+     +---------+     +---------+
    

    在这种情况下,至少有两个单独的查询无疑更好。如果您尝试连接标签和 cmets,因为两者之间没有直接关系,您最终会得到标签和注释的所有可能组合。 many * many == manymany。除此之外,由于帖子和标签不相关,您可以并行执行这两个查询,从而获得潜在收益。

    不过,让我们考虑一个不同的场景:您希望将 cmets 附加到帖子中,以及评论者的联系信息。

     +----------+     +------------+     +---------+
     |   user   |     |  comment   |     |   post  |
     |----------|1   *|------------|*   1|---------|
     | user_id  |-----| post_id    |-----| post_id |
     | username |     | user_id    |     | ...     |
     | ...      |     | ...        |     +---------+
     +----------+     +------------+
    

    这是您应该考虑加入的地方。除了是一个更自然的查询之外,大多数数据库系统(包括 MySQL)都有很多聪明的人投入大量精力来优化查询,就像它一样。对于单独的查询,由于每个查询都依赖于前一个查询的结果,所以查询不能并行进行,总时间不仅变成了查询的实际执行时间,还变成了获取结果、筛选的时间通过它们获取下一个查询的 ID,将行链接在一起等。

    【讨论】:

    • 如果您在第二种情况下检索了很多用户列(并且同一用户多次发表评论),这仍然会留下一个问题,即是否最好在单独的查询中检索它们。
    • @AdrianBaker:就像我说的,很多聪明人都付出了很多努力。如果我要优化我的 SQL 服务器,我的第一个想法是使用压缩,这将消除大量的冗余,根本没有改变代码。下一级优化将包括将结果重新组织到表格中,并将这些结果与行 ID 元组一起发送,然后客户端库可以根据需要轻松地在其一侧组装。
    • 这两种优化都可以通过连接来减少甚至消除冗余,但对于获取相关记录必须执行的固有串行查询没有太多帮助。
    【解决方案3】:

    真正的问题是:这些记录是一对一关系还是一对多关系

    TLDR 答案:

    如果是一对一的,请使用JOIN 语句。

    如果是一对多,则使用一个(或多个)SELECT 语句进行服务器端代码优化。

    为什么以及如何使用 SELECT 进行优化

    SELECT'ing(使用多个查询而不是连接)基于一对多关系对大量记录产生最佳效率,因为JOIN'ing 存在指数内存泄漏问题。抓取所有数据,然后使用服务器端脚本语言对其进行整理:

    SELECT * FROM Address WHERE Personid IN(1,2,3);
    

    结果:

    Address.id : 1            // First person and their address
    Address.Personid : 1
    Address.City : "Boston"
    
    Address.id : 2            // First person's second address
    Address.Personid : 1
    Address.City : "New York"
    
    Address.id : 3            // Second person's address
    Address.Personid : 2
    Address.City : "Barcelona"
    

    在这里,我在一个 select 语句中获取所有记录。这比JOIN 好,后者将一次获取一小组这些记录,作为另一个查询的子组件。然后我用看起来像...的服务器端代码解析它。

    <?php
        foreach($addresses as $address) {
             $persons[$address['Personid']]->Address[] = $address;
        }
    ?>
    

    何时不使用 JOIN 进行优化

    JOIN'基于与单个记录的一对一关系对一大组记录进行处理,与多个SELECT 语句相比,一个接一个地获得最佳效率,后者只需获取下一个记录类型.

    但是JOIN 在获取具有一对多关系的记录时效率很低。

    示例:数据库 Blogs 有 3 个感兴趣的表,Blogpost、Tag 和 Comment。

    SELECT * from BlogPost
    LEFT JOIN Tag ON Tag.BlogPostid = BlogPost.id
    LEFT JOIN Comment ON Comment.BlogPostid = BlogPost.id;
    

    如果有 1 篇博文、2 个标签和 2 个 cmets,您将得到如下结果:

    Row1: tag1, comment1,
    Row2: tag1, comment2,
    Row3: tag2, comment1,
    Row4: tag2, comment2,
    

    注意每条记录是如何复制的。好的,所以,2 cmets 和 2 tags 是 4 行。如果我们有 4 个 cmets 和 4 个标签怎么办?不是 8 行,而是 16 行:

    Row1: tag1, comment1,
    Row2: tag1, comment2,
    Row3: tag1, comment3,
    Row4: tag1, comment4,
    Row5: tag2, comment1,
    Row6: tag2, comment2,
    Row7: tag2, comment3,
    Row8: tag2, comment4,
    Row9: tag3, comment1,
    Row10: tag3, comment2,
    Row11: tag3, comment3,
    Row12: tag3, comment4,
    Row13: tag4, comment1,
    Row14: tag4, comment2,
    Row15: tag4, comment3,
    Row16: tag4, comment4,
    

    添加更多的表、更多的记录等,问题将迅速膨胀到数百行,这些行都充满了大部分冗余数据。

    这些重复的成本是多少?内存(在 SQL 服务器和尝试删除重复项的代码中)和网络资源(在 SQL 服务器和您的代码服务器之间)。

    来源:https://dev.mysql.com/doc/refman/8.0/en/nested-join-optimization.htmlhttps://dev.mysql.com/doc/workbench/en/wb-relationship-tools.html

    【讨论】:

    • 你没有抓住重点。这不是一对(一对|多)。这是关于将行集配对在一起是否有意义。您只需要两组切向相关的数据。如果您要询问 cmets 以及他们的作者的联系信息,那么作为加入更有意义,即使人们可能会写多个评论。
    • @cHao:感谢您的评论。我上面的答案是这里找到的 MySQL 文档的摘要:dev.mysql.com/doc/workbench/en/wb-relationship-tools.html
    • 我想指出,这个问题在数学上甚至比这个答案所表明的更重要。 @HoldOffHunger 指出你得到 16 行而不是 8 行。这是看待它的一种方式。但实际上,如果您查看数据冗余,您将获得 32 个数据点而不是 8 个。它已经是 4 倍的数据,仅 2 个连接!!!!!!如果您再添加一个连接来制作 3,那将变得非常荒谬!
    • 如果您加入第 3 列,该列为 @HoldOffHunger 已经演示的每对返回 4 条额外记录,从技术上讲,您将只有 12 个有意义的数据点,但您将有 64 行和 192 个数据点.
    • 还有一点值得指出:更多内存 = 更慢的性能。与缓存数据上的处理器周期相比,内存非常慢。任何使应用程序不得不消耗更多内存的事情也会使其实际处理速度变慢。
    【解决方案4】:

    这是一个包含 100 个有用查询的链接,这些是在 Oracle 数据库中测试的,但请记住 SQL 是一个标准,Oracle、MS SQL Server、MySQL 和其他数据库之间的区别在于 SQL 方言:

    http://javaforlearn.com/100-sql-queries-learn/

    【讨论】:

      【解决方案5】:

      有几个因素意味着没有二元答案。什么最适合性能的问题取决于您的环境。顺便说一句,如果您的带有标识符的单选不是亚秒级的,那么您的配置可能有问题。

      真正要问的问题是您希望如何访问数据。单选支持后期绑定。例如,如果您只需要员工信息,您可以从员工表中进行选择。外键关系可用于稍后根据需要检索相关资源。选择已经有一个指向的键,所以它们应该非常快,你只需要检索你需要的东西。必须始终考虑网络延迟。

      联接将一次检索所有数据。如果您正在生成报告或填充网格,这可能正是您想要的。在这种情况下,编译和优化的连接只会比单选快。请记住,临时连接可能没有那么快——您应该将它们编译(到存储过程中)。速度答案取决于执行计划,该计划详细说明了 DBMS 检索数据所采取的步骤。

      【讨论】:

        【解决方案6】:

        做了一个快速测试,从 50,000 行表中选择一行并与 100,000 行表中的一行连接。基本上看起来像:

        $id = mt_rand(1, 50000);
        $row = $db->fetchOne("SELECT * FROM table1 WHERE id = " . $id);
        $row = $db->fetchOne("SELECT * FROM table2 WHERE other_id = " . $row['other_id']);
        

        $id = mt_rand(1, 50000);
        $db->fetchOne("SELECT table1.*, table2.*
            FROM table1
            LEFT JOIN table1.other_id = table2.other_id
            WHERE table1.id = " . $id);
        

        两次选择方法需要 3.7 秒来读取 50,000 次,而在我家的慢速计算机上 JOIN 需要 2.0 秒。 INNER JOIN 和 LEFT JOIN 没有区别。获取多行(例如,使用 IN SET)产生了类似的结果。

        【讨论】:

        • 如果选择一页行(如 20 或 50 行),就像典型的 Web 视图网格一样,并将单个 LEFT JOIN 与两个查询进行比较 - 选择 2 或 3 个标识符一些 WHERE 条件,然后使用 IN() 运行另一个 SELECT 查询。
        • 列 id 和 other_id 是否被索引?
        【解决方案7】:

        根据我的经验,我发现运行多个查询通常更快,尤其是在检索大型数据集时。

        当从另一个应用程序(例如 PHP)与数据库交互时,存在一次访问服务器多于多次的参数。

        还有其他方法可以限制访问服务器的次数,并且仍然运行多个查询,这些查询通常不仅更快,而且使应用程序更易于阅读 - 例如 mysqli_multi_query。

        对于 SQL,我不是新手,我认为开发人员,尤其是初级开发人员倾向于花费大量时间尝试编写非常聪明的连接,因为它们看起来很聪明,而实际上有一些聪明的方法来提取看起来很简单的数据。

        最后一段是个人意见,但我希望这会有所帮助。我确实同意其他人的观点,尽管他们说你应该进行基准测试。这两种方法都不是灵丹妙药。

        【讨论】:

        • 是的,我们不仅要考虑查询本身,还要考虑应用程序内部的数据处理。如果使用外部连接获取数据,则存在一些冗余(有时它会变得非常大),必须由应用程序(通常在某些 ORM 库中)进行排序,因此总的来说,带有 JOIN 查询的单个 SELECT 可能会消耗更多 CPU 和比两个简单的 SELECT 花费的时间
        【解决方案8】:

        我实际上是自己来寻找这个问题的,在阅读了给定的答案后,我只能同意比较数据库查询性能的最佳方法是获取真实世界的数字,因为有很多变量需要考虑到但是,我也认为比较它们之间的数字在几乎所有情况下都没有好处。我的意思是,这些数字应该始终与可接受的数字进行比较,绝对不能相互比较。

        如果一种查询方式需要 0.02 秒而另一种查询方式需要 20 秒,我可以理解,这是一个巨大的差异。但是,如果一种查询方式需要 0.0000000002 秒,而另一种查询方式需要 0.0000002 秒呢?在这两种情况下,一种方式比另一种方式快 1000 倍,但它真的在第二种情况下仍然“惊人”吗?

        我个人认为的底线是:如果它表现良好,那就选择简单的解决方案。

        【讨论】:

        • 当然,这取决于您是否计划扩展。因为当 facebook 刚开始时,我确信他们有这类查询,但考虑到扩展并寻求更有效但可能更复杂的解决方案。
        • @dudewad 有道理。这一切都取决于你最终需要什么。
        • 哈哈是的...因为在谷歌,1 纳秒的损失实际上等于 100 亿美元...但这只是谣言。
        • @dudewad 实际上,当 Facebook 刚成立时,我保证他们采用了更简单的解决方案。扎克伯格说他只用了 2 周就编写了第一个版本。初创企业需要快速参与竞争,而幸存下来的企业通常不会担心规模扩大,直到他们真正需要它。 然后他们在拥有数百万美元的投资后重构东西,并且可以聘请专门研究性能的摇滚明星程序员。就您的观点而言,我希望 Facebook 现在经常采用更复杂的解决方案来获得微小的性能提升,但我们大多数人都不会为 Facebook 编程。
        【解决方案9】:

        这太模糊了,无法为您提供与您的具体案例相关的答案。这取决于很多事情。 Jeff Atwood(本网站的创始人)实际上是wrote about this。但是,在大多数情况下,如果您拥有正确的索引并且正确地执行了 JOIN,那么执行 1 次行程通常会比执行多次行程更快。

        【讨论】:

        • 如果您在不同的键上连接 3 个或更多表,通常数据库(即 mysql)每个表只能使用一个索引,这意味着其中一个连接可能会很快(并使用索引),而其他的会非常慢。对于多个查询,您可以优化索引以用于每个查询。
        • 我认为这取决于您对“更快”的定义......例如,3 PK 内部连接可能比 4 次往返更快,因为网络开销,并且因为您需要停止并在前一个查询完成后准备并发送每个查询。但是,如果您要对负载下的服务器进行基准测试,在大多数情况下,与 PK 查询相比,连接会花费更多的 CPU 时间,并且通常还会导致更多的网络开销。
        【解决方案10】:

        对于内部连接,单个查询是有意义的,因为您只会得到匹配的行。 对于左连接,多个查询要好得多......看看我做的以下基准:

        1. 5 个连接的单个查询

          查询:8.074508 秒

          结果大小:2268000

        2. 连续 5 个查询

          组合查询时间:0.00262秒

          结果大小:165(6 + 50 + 7 + 12 + 90)

        .

        请注意,我们在两种情况下得到相同的结果 (6 x 50 x 7 x 12 x 90 = 2268000)

        左连接使用冗余数据成倍增加内存。

        如果你只连接两个表,内存限制可能不会那么糟糕,但通常是三个或更多,它变得值得不同的查询。

        附带说明,我的 MySQL 服务器就在我的应用程序服务器旁边……所以连接时间可以忽略不计。如果您的连接时间在几秒钟内,那么也许会有好处

        弗兰克

        【讨论】:

        • 如果我们抛开一个恼人的小事实,即没有人在他们正常的头脑中进行 5 个表之间的交叉连接(正因为如此,在大多数情况下,它只是不会感觉),你的“基准”可能有一些优点。但是左连接或内连接是常态,通常是通过键(使检索更快),并且数据的重复通常比您想象的要少得多,
        • @cHao 说谁?我刚刚查看了 SMF 和 phpBB 并看到了 3 个表之间的 JOIN - 如果您添加插件或修改,它们可以轻松添加到其中。任何类型的大型应用程序都有可能进行许多 JOIN。可以说,一个写得不好/误用的 ORM 可能会加入它实际上不需要的表(甚至可能是每个表)。
        • @NathanAdams:左连接和内连接一点也不差。 (事实上​​,如果你不在这里和那里连接表,那么你做的 SQL 是错误的。)我说的是 cross joins,即使在两个表之间也几乎总是不可取的,让仅 5 - 这将是获得上述其他完全虚假的“2268000”结果的唯一方法。
        • 看看结果吧。 “结果大小:2268000”与“结果大小:165”。我认为您使用 JOIN 的速度变慢是因为您的记录彼此之间存在一对多的关系,而如果它们具有一对一的关系,则 JOIN 绝对会快得多,而且肯定不会有结果尺寸大于 SELECT。
        • @cHao 显然你在第一次发表评论时还没有见过 Magento
        【解决方案11】:

        在吞吐量方面会更快吗?大概。但它也可能一次锁定更多数据库对象(取决于您的数据库和架构),从而降低并发性。根据我的经验,人们经常被“更少的数据库往返”论点误导,而实际上在大多数 OLTP 系统上,数据库位于同一 LAN 上,真正的瓶颈很少是网络。

        【讨论】:

          【解决方案12】:

          是的,使用 JOINS 的查询会更快。尽管不知道您要查询的表的关系、数据集的大小或主键的位置,但几乎不可能说出速度快了多少。

          为什么不测试这两种情况,然后你肯定会知道......

          【讨论】:

            【解决方案13】:

            根据数据库的复杂性与开发人员的复杂性相比,执行许多 SELECT 调用可能更简单。

            尝试针对 JOIN 和多个 SELECTS 运行一些数据库统计信息。查看在您的环境中,JOIN 是否比 SELECT 快/慢。

            再一次,如果将其更改为 JOIN 意味着额外的一天/一周/一个月的开发工作,我会坚持使用多个 SELECT

            干杯,

            BLT

            【讨论】:

              【解决方案14】:

              构造单独的查询和连接,然后对它们中的每一个进行计时——没有什么比真实的数字更有帮助了。

              然后更好——在每个查询的开头添加“EXPLAIN”。这将告诉您 MySQL 使用多少子查询来回答您的数据请求,以及为每个查询扫描了多少行。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2014-12-17
                • 1970-01-01
                • 2021-01-28
                • 2023-03-17
                • 2015-06-03
                • 2013-01-15
                • 1970-01-01
                相关资源
                最近更新 更多