简体   繁体   中英

Inefficient execution plan on MySQL query with multiple joins

I have a performance issue with MySQL ; it seems execution plan for my request is far from optimal, but I don't know why MySQL chooses it nor how I could change it. I reproduced the issue within a minimal environment, and here is the query:

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

And here are the tables used in this query:

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

Now here is execution plan, obtained with "EXPLAIN <the query above>":

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

As you can see it starts by scanning the entire "section" table, using a temporary table and causing terrible performances. I don't really understand why this happens, since an index exists on both "topic.last_time" (used in WHERE clause) and "section.id" (used in first INNER JOIN). I also did several tests and this result is pretty unstable:

  • If I add an explicit "FORCE INDEX" statement on the "topic" table then obviously MySQL correctly uses indices "topic.last_time" and "section.id", ending up with a much faster result as you can see below (but I can't generate this kind of MySQL specific extension from the SQL query generation library I'm using)
  • If I replace first "INNER JOIN" (against "section" table) by a "LEFT JOIN" I have the same result, probably because it prevents MySQL from reversing operands of the JOIN (but LEFT JOIN is not what I'd like to express) ;
  • Weirder: if I remove index "section__last_time" from table "topic", then I also get the same result. I really don't understand why this index has an impact on execution plan? (and anyway I need it for other queries so I can't remove it)

Here is the execution plan I have after applying any of the three changes above:

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

I also tried to "OPTIMIZE TABLE" all tables or switch to InnoDB engine but neither of those changed anything. Issue was reproduced on MySQL versions 5.5.35 and 5.6.15 ; I also uploaded a snapshot of the test environment here , where the queries above can be easily reproduced.

Do you know what could explain this execution plan?

Consider adding an index on section.last_member, section.id.

ALTER TABLE section ADD KEY(last_member, id);

If they are innodb you can omit ID since it is already a PK.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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