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

时间:2015-01-29 00:16:29

标签: mysql performance innodb database-performance

我不了解MySQL(InnoDB)对我的查询做了什么。我有一个查询从两个表中提取数据,它运行在~35毫秒。如果我在没有LEFT JOIN的情况下运行查询,它会在~2.5 ms内完成。甚至对LEFT JOIN所做的“等效”查询也需要~0.5 ms。为什么呢?

“慢”查询如下:

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`;

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

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

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

2 个答案:

答案 0 :(得分:0)

不同之处在于正在访问表的订单

LEFT JOIN外部联接,它必须从左侧的表中返回行,而右侧的表中没有匹配的行。< / p>

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

使用外部联接,MySQL无法使用右侧的表作为驱动程序,因为左侧的表中可能还有一些行也需要返回。< / p>

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

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

GROUP BY Action.user_assigned_id的目的是什么?

如果您正在讨论的LEFT JOIN查询(我们未在问题中看到)与INNER JOIN相同,只需将INNER关键字替换为{{ 1}}关键字...

使用LEFT,有时MySQL可以有效地使用带有前导列GROUP BY col的索引来避免“使用filesort”操作,但在您的情况下,有一个col一个不同的表达,所以我认为没有办法绕过“使用filesort”操作。

你最好的选择可能是确保你有一个适当的索引来满足WHERE子句中的谓词,如果它将行限制为表中的一小部分行。

ORDER BY

MySQL应该能够将该索引用于... ON `actions` (`company_id`, `action_date`, `user_assigned_id`, `id`) 上的等式谓词,以及company_id上的范围扫描操作。索引中的其他两列使得覆盖索引成为覆盖索引,因此可以完全从索引中完成查询,而无需查找基础表中的数据页。

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

答案 1 :(得分:0)

我会在单个列user_assigned_id上添加一个INDEX,因为多个列索引仅在索引的所有列或仅在第一列上按索引顺序进行查询时才可用所以重新排序你的索引可能也有效:

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是多列索引的最后一列。