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 中的子查询?的主要内容,如果未能解决你的问题,请参考以下文章