繁体   English   中英

具有多个联接的MySQL查询的执行效率低下的计划

[英]Inefficient execution plan on MySQL query with multiple joins

我的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           |
+----+-------------+--------------+--------+------------------------------+--------------------+---------+-------------------------------+------+---------------------------------+

如您所见,它是通过扫描整个“ section”表开始的,使用一个临时表并导致了糟糕的性能。 我真的不明白为什么会发生这种情况,因为“ 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不是我想要的)表达) ;
  • 怪异的:如果我从表“主题”中删除索引“ 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 |
+----+-------------+--------------+--------+---------------+-----------+---------+-------------------------------+------+-------------+

我还尝试“优化表”所有表或切换到InnoDB引擎,但这些表均未更改。 问题在MySQL 5.5.35和5.6.15版本上已被复制; 我还在此处上传了测试环境的快照,可以轻松重现上面的查询。

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

考虑在section.last_member,section.id上添加索引。

ALTER TABLE section ADD KEY(last_member, id);

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

暂无
暂无

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

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