繁体   English   中英

为什么 LEFT JOIN 和 GROUP BY 会影响性能?

[英]Why the performance is affected with a LEFT JOIN and a GROUP BY?

我不明白 MySQL (InnoDB) 对我的查询做了什么。 我有一个查询要从两个表中提取数据,它运行时间约为 35 毫秒。 如果我在没有 LEFT JOIN 的情况下运行查询,它会在 ~2.5 毫秒内完成。 即使是对 LEFT JOIN 正在做什么的“等效”查询也需要大约 0.5 毫秒。 为什么?

“慢”查询如下:

SELECT
    `Assigned`.`id`,
    `Assigned`.`name`,
    (COUNT(`Action`.`id`)) AS `Action__total_actions`

FROM `actions` AS `Action`

LEFT JOIN `users` AS `Assigned` ON (`Assigned`.`id` = `Action`.`user_assigned_id`)

WHERE
    `Action`.`company_id` = 1 AND
    `Action`.`action_date` BETWEEN '2014-12-28 00:00:00' AND '2015-01-28 23:59:59'

GROUP BY `Action`.`user_assigned_id`
ORDER BY `Assigned`.`name` ASC;

我有一个表用户的主索引和表操作的下一个索引:

ALTER TABLE `actions` ADD INDEX `actions_report_by_assigned` (`company_id`, `action_date`, `user_assigned_id`);

这是它变得奇怪的时候。 如果我“提取” LEFT JOIN,索引仍然有效(对于两个查询),但下一个要快 10 倍:

SELECT
    `Action`.`user_assigned_id`,
    (COUNT(`Action`.`id`)) AS `Action__total_actions`

FROM `actions` AS `Action`
WHERE
    `Action`.`company_id` = 1 AND
    `Action`.`action_date` BETWEEN '2014-12-28 00:00:00' AND '2015-01-28 23:59:59'

GROUP BY `Action`.`user_assigned_id`
ORDER BY `Action`.`user_assigned_id`;

我认为索引设计得很好,因为两个查询 go 通过相同的总行数进行计数。 EXPLAIN 命令告诉我它正在使用的索引,但它还在额外的列中说:“ Using where; 使用索引; 使用临时的; 在两个查询中都使用 filesort ”(此外,一个要快 10 倍)。

也许是我的 LEFT JOIN 的文件排序,因为如果我从我的第一个查询中删除 GROUP,它会加速到 ~15 毫秒。 可悲的是,我不能那样做。 我错过了什么吗?

我应该忽略这个吗? 解决它的最佳方法是什么?

不同之处在于访问表的顺序

LEFT JOIN是一个外部 LEFT JOIN ,它必须返回左侧表中的行,而右侧表中没有匹配的行。

INNER JOIN只返回匹配的行,因此MySQL只需查找匹配的行,因此它可以将任一表用作嵌套循环操作的驱动程序,通常,MySQL将使用返回较少行的表。

使用外部连接 ,MySQL不能将右侧的表用作驱动程序,因为左侧的表中可能还需要返回一些行。

这就是为什么 至于如何解决...

GROUP BY子句中有一个表达式而不返回该表达式有点奇怪。 (在SQL中执行此操作是有效的,但是客户端如何知道哪一行是GROUP BY表达式的哪个值?)

GROUP BY Action.user_assigned_id的用途是什么?

如果您要谈论的LEFT JOIN查询(我们在问题中没有看到)与INNER JOIN相同,只需将INNER关键字替换为LEFT关键字即可。

使用GROUP BY col ,有时MySQL可以有效地使用前导列col的索引来避免“使用文件排序”操作,但是在您的情况下,在不同的表达式上有一个ORDER BY ,所以我认为没有任何解决“使用文件排序”操作的方法。

最好的选择可能是确保您有合适的索引来满足WHERE子句中的谓词,如果这样做会将行限制为表中行的一小部分。

... ON `actions` (`company_id`, `action_date`, `user_assigned_id`, `id`)

MySQL应该能够将该索引用于company_id上的相等谓词,以及对action_date进行范围扫描操作。 索引中的其他两列构成了覆盖索引,因此可以完全从索引中满足查询,而无需对基础表中的数据页进行任何查找。

如果是这种情况,EXPLAIN输出中的Extra列将显示“ Using index”。

我会在单列user_assigned_id上添加一个INDEX,因为仅当对索引的所有列或仅对前几列进行查询时,才按索引的user_assigned_id多列索引,因此您可能需要对索引重新排序也可以:

ALTER TABLE `actions` ADD INDEX `actions_report_by_assigned` (`user_assigned_id`, `company_id`, `action_date`); 

参见http://dev.mysql.com/doc/refman/5.0/en/multiple-column-indexes.html

例如,如果在(col1,col2,col3)上有一个三列索引,则在(col1),(col1,col2)和(col1,col2,col3)上都有索引搜索功能。

目前,您的actions_report_by_assigned INDEX不能用于此JOIN:

INNER JOIN `users` AS `Assigned` ON (`Assigned`.`id` = `Action`.`user_assigned_id`)

因为user_assigned_id是多列索引的最后一列。

不要在大表上使用左连接。 提示:将查询拆分成更小的部分。 5分钟查询将执行不到1秒试试看

还要检查解释计划。 获取连接中涉及的字段。 检查是否在连接字段的两侧都应用了索引。 再次检查解释计划,您可以看到计数减少了。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM