[英]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.