SELECT 中的子查询或 JOIN 中的子查询?

Posted

技术标签:

【中文标题】SELECT 中的子查询或 JOIN 中的子查询?【英文标题】:Subquery in SELECT or Subquery in 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

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

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

【问题讨论】:

【参考方案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 子句中使用连接谓词,就不会发生这种情况。

以上是关于SELECT 中的子查询或 JOIN 中的子查询?的主要内容,如果未能解决你的问题,请参考以下文章

MYSQLupdate/delete/select语句中的子查询

BIGQUERY:连接谓词中的表不受支持的子查询

JOIN 替代 SELECT 子查询

SELECT 子句中的子查询

SELECT 中的子查询:尝试移至主查询

Select查询Ms访问中的子查询