具有多个连接的MySQL查询的低效执行计划

时间:2015-06-16 17:08:23

标签: mysql sql join

我遇到了MySQL的性能问题;似乎我的请求的执行计划远非最佳,但我不知道为什么MySQL选择它,也不知道如何改变它。我在最小的环境中重现了这个问题,这是查询:

SELECT member.id, member_cache.id, section.id, topic.id
FROM topic
INNER JOIN (section
    INNER JOIN (member
        LEFT JOIN (member_cache) ON member_cache.id = member.id
    ) ON member.id = section.last_member
) ON section.id = topic.section
WHERE topic.last_time IS NOT NULL
ORDER BY topic.last_time DESC
LIMIT 0, 1

以下是此查询中使用的表格:

CREATE TABLE `member` (`id` int(10) unsigned NOT NULL)
    ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE `member_cache` (`id` int(10) unsigned NOT NULL)
    ENGINE=MyISAM DEFAULT CHARSET=utf8;
CREATE TABLE `section` (`id` int(10) unsigned NOT NULL, `last_member` int(10) unsigned NOT NULL DEFAULT '0')
    ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE `topic` (`id` int(10) unsigned NOT NULL, `section` int(10) unsigned NOT NULL, `last_time` int(10) unsigned NOT NULL)
    ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

ALTER TABLE `member` ADD PRIMARY KEY (`id`);
ALTER TABLE `member_cache` ADD PRIMARY KEY (`id`);
ALTER TABLE `section`ADD PRIMARY KEY (`id`);
ALTER TABLE `topic` ADD PRIMARY KEY (`id`), ADD KEY `section__last_time` (`section`,`last_time`), ADD KEY `last_time` (`last_time`);

现在这里是执行计划,使用“EXPLAIN<上面的查询>”获得:

+----+-------------+--------------+--------+------------------------------+--------------------+---------+-------------------------------+------+---------------------------------+
| id | select_type | table        | type   | possible_keys                | key                | key_len | ref                           | rows | Extra                           |
+----+-------------+--------------+--------+------------------------------+--------------------+---------+-------------------------------+------+---------------------------------+
|  1 | SIMPLE      | section      | ALL    | PRIMARY                      | NULL               | NULL    | NULL                          | 2188 | Using temporary; Using filesort |
|  1 | SIMPLE      | member       | eq_ref | PRIMARY                      | PRIMARY            | 4       | temporary.section.last_member |    1 | Using index                     |
|  1 | SIMPLE      | member_cache | eq_ref | PRIMARY                      | PRIMARY            | 4       | temporary.section.last_member |    1 | Using index                     |
|  1 | SIMPLE      | topic        | ref    | section__last_time,last_time | section__last_time | 4       | temporary.section.id          |  106 | Using index condition           |
+----+-------------+--------------+--------+------------------------------+--------------------+---------+-------------------------------+------+---------------------------------+

正如您所看到的那样,它首先扫描整个“部分”表,使用临时表并导致糟糕的表现。我真的不明白为什么会发生这种情况,因为“topic.last_time”(在WHERE子句中使用)和“section.id”(在第一个INNER JOIN中使用)都存在索引。我也做了几次测试,结果非常不稳定:

  • 如果我在“topic”表上添加一个明确的“FORCE INDEX”语句,那么很明显MySQL正确使用索引“topic.last_time”和“section.id”,结果会更快,如下所示(但我无法从我正在使用的SQL查询生成库中生成这种特定于MySQL的扩展)
  • 如果我用“LEFT JOIN”替换第一个“INNER JOIN”(对“section”表),我得到相同的结果,可能是因为它阻止MySQL反转JOIN的操作数(但LEFT JOIN不是我' d喜欢表达;;
  • Weirder:如果我从表“topic”中删除索引“section__last_time”,那么我也得到相同的结果。我真的不明白为什么这个指数对执行计划有影响? (无论如何,我需要它用于其他查询,所以我无法删除它)

以下是我应用上述三项变更后的执行计划:

+----+-------------+--------------+--------+---------------+-----------+---------+-------------------------------+------+-------------+
| id | select_type | table        | type   | possible_keys | key       | key_len | ref                           | rows | Extra       |
+----+-------------+--------------+--------+---------------+-----------+---------+-------------------------------+------+-------------+
|  1 | SIMPLE      | topic        | index  | last_time     | last_time | 4       | NULL                          |    1 | Using where |
|  1 | SIMPLE      | section      | eq_ref | PRIMARY       | PRIMARY   | 4       | temporary.topic.section       |    1 | NULL        |
|  1 | SIMPLE      | member       | eq_ref | PRIMARY       | PRIMARY   | 4       | temporary.section.last_member |    1 | Using index |
|  1 | SIMPLE      | member_cache | eq_ref | PRIMARY       | PRIMARY   | 4       | temporary.section.last_member |    1 | Using index |
+----+-------------+--------------+--------+---------------+-----------+---------+-------------------------------+------+-------------+

我还试图“OPTIMIZE TABLE”所有表格或切换到InnoDB引擎,但这些都没有改变任何东西。问题转载于MySQL版本5.5.35和5.6.15;我还上传了测试环境here的快照,其中可以轻松复制上述查询。

你知道什么可以解释这个执行计划吗?

1 个答案:

答案 0 :(得分:0)

考虑在section.last_member,section.id。

上添加索引
ALTER TABLE section ADD KEY(last_member, id);

如果他们是innodb,你可以省略ID,因为它已经是PK。