【问题标题】:Subquery in SELECT or Subquery in JOIN?SELECT 中的子查询或 JOIN 中的子查询?
【发布时间】:2015-02-11 16:45:05
【问题描述】:

我有一个这种形式的 MYSQL 查询:

SELECT
    employee.name,
    totalpayments.totalpaid
FROM
    employee
    JOIN (
        SELECT
            paychecks.employee_id,
            SUM(paychecks.amount) totalpaid
        FROM
            paychecks
        GROUP BY
            paychecks.employee_id
         ) totalpayments on totalpayments.employee_id = employee.id

我最近发现这种形式的返回速度要快得多:

SELECT
    employee.name,
    (
        SELECT
            SUM(paychecks.amount)
        FROM
            paychecks
        WHERE
            paychecks.employee_id = employee.id
    ) totalpaid
FROM
    employee

令我惊讶的是速度会有所不同,并且较低的查询会更快。我更喜欢上面的形式进行开发,因为我可以独立运行子查询。

有没有办法获得“两全其美”:快速返回结果并能够单独运行子查询?

【问题讨论】:

    标签: mysql correlated-subquery scalar-subquery


    【解决方案1】:

    可能,相关子查询能够有效利用索引,这就是它速度快的原因,即使该子查询必须执行多次。

    对于带有内联视图的第一个查询,这会导致 MySQL 创建一个派生表,对于大型集合,这实际上是一个 MyISAM 表。

    在 MySQL 5.6.x 及更高版本中,优化器可以选择在派生表上添加索引,如果这将允许 ref 操作并且ref 操作的估计成本低于嵌套循环扫描.

    我建议您尝试使用EXPLAIN 查看访问计划。 (根据您的性能报告,我怀疑您运行的是 MySQL 5.5 或更早版本。)


    如果employees 中有行而paychecks 中没有匹配的行,这两个语句并不完全等价。

    完全避免子查询可以获得等价的结果:

    SELECT e.name
         , SUM(p.amount) AS total_paid
      FROM employee e 
      JOIN paychecks p
        ON p.employee_id = e.id
     GROUP BY e.id
    

    (使用内连接获得相当于第一个查询的结果,使用LEFT外连接相当于第二个查询。如果要返回,请将 SUM() 聚合包装在 IFNULL 函数中如果在 paychecks 中找不到具有非空值 amount 的匹配行,则返回零而不是空值。)

    【讨论】:

    • 非常感谢,斯宾塞7593!我可以尝试将索引强制到派生表上吗? (我对索引或创建它们的语法不是很熟悉。)
    • @YossiFendel:我不相信在派生表上创建索引有任何提示。这只会在 5.6 及更高版本中发生。 EXPLAIN 输出应该显示正在使用哪种连接操作。完全避免子查询可能会获得最佳性能。我在回答的编辑中提供了一个示例。
    • @YossiFendel:另一种选择是创建一个临时表(带有索引)作为子查询的结果,然后在第二个查询中引用该临时表。这很麻烦,但它可以提高性能,尤其是在多个查询中引用同一个内联视图时……因为我们避免多次实现它。
    • 感谢您的持续帮助。出于这个论坛问题的目的,我将查询简化为似乎更容易理解的内容。我希望我可以完全避免子查询,但在这种情况下(有几个这样的聚合)是不可能的。您可能是对的,索引在底层版本中加快了速度。在进一步研究之前,我可能需要了解更多关于如何阅读 EXPLAIN 输出的信息。
    • @YossiFendel:对于 MySQL 5.5,8.8 Understanding the Query Execution Plan。 (每个版本(5.1、5.5、5,6)都有更改,因此请务必查看正确版本的文档。除非您了解 MySQL 可用的操作以及这些操作是如何进行的,否则 EXPLAIN 输出实际上没有意义在 EXPLAIN 输出中表示。)
    【解决方案2】:

    Join 基本上是笛卡尔积,这意味着表 A 的所有记录将与表 B 的所有记录合并。输出将是

    number of records of table A * number of records of table b =rows in the new table
    10 * 10 = 100
    

    在这 100 条记录中,与过滤器匹配的记录将在查询中返回。

    在嵌套查询中,有一个示例内部查询,无论内部查询的总记录大小是多少,都将作为外部查询的输入,这就是为什么嵌套查询比联接更快。

    【讨论】:

    • 是的,有时,SELECT 列表中的相关子查询比连接操作更快,但通常情况并非如此。对创建笛卡尔积(m * n 行)然后过滤掉行的连接操作的描述并不是对 JOIN 操作实际操作方式的完全准确描述。要真正获得您描述的行为,您需要编写一些导致创建笛卡尔积的内容,例如:SELECT a.id, b.id FROM a JOIN b HAVING a.id = b.id。但是在WHERE 子句或ON 子句中使用连接谓词,就不会发生这种情况。
    猜你喜欢
    • 1970-01-01
    • 2017-06-08
    • 2021-10-14
    • 2014-02-23
    • 1970-01-01
    • 1970-01-01
    • 2013-02-11
    • 2022-11-04
    • 1970-01-01
    相关资源
    最近更新 更多