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

多连接MySQL查询的低效执行计划,mysql,sql,join,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 =

我有一个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`);
下面是执行计划,通过“解释”获得:

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

  • 如果我在“topic”表中添加了一个显式的“FORCE INDEX”语句,那么显然MySQL正确地使用了“topic.last_time”和“section.id”索引,结果会更快,如下所示(但我无法从我使用的SQL查询生成库生成这种特定于MySQL的扩展)
  • 如果我用“左连接”替换第一个“内部连接”(相对于“section”表),我会得到相同的结果,可能是因为它阻止MySQL反转连接的操作数(但左连接不是我想要表达的)
  • 更奇怪的是:如果我从表“topic”中删除索引“section\u last\u 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 |
+----+-------------+--------------+--------+---------------+-----------+---------+-------------------------------+------+-------------+
我还尝试“优化表”所有表或切换到InnoDB引擎,但这两个都没有改变任何事情。该问题在MySQL版本5.5.35和5.6.15上重现;我还上传了测试环境的快照,上面的查询可以很容易地重现


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

考虑在section.last_成员section.id上添加索引

ALTER TABLE section ADD KEY(last_member, id);

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

确实有效,谢谢!您知道为什么在没有这个额外索引的情况下选择带全表扫描的执行计划吗?我真的不明白这样一个低效的解决方案怎么会被MySQL认为是最好的解决方案。(我想知道如何使您的解决方案适应我的现实世界场景)MySQL的旧版本有非常简单的计划优化器。如果没有二级索引,分区表将与topic OK联接,但成员将成为非索引联接,从而导致完整表扫描(我认为)。没有任何附加索引,它可以通过
topic.last_time
(index)然后
section.id
(PK)、
member.id
(PK)和
member\u cache.id
(PK);如果我明确地强制使用其中一个索引,而不必更新模式,就会发生这种情况。我仍然不太明白,但无论如何,感谢您的帮助:)
ALTER TABLE section ADD KEY(last_member, id);