MySQL性能,子查询使用临时文件排序,当查询使用order by / group by时

时间:2015-01-15 02:52:47

标签: mysql

在为用户创建的游戏地图的存档制作标签表时,用于获取包含所有提供的标签的地图的地图ID的SQL是,其中......是标签,#是标签的数量:

SELECT DISTINCT map_id 
FROM `map_tag` 
INNER JOIN `tag` USING (tag_id) 
WHERE tag IN (...) 
GROUP BY map_id HAVING COUNT(DISTINCT tag_id) = #
ORDER BY map_id DESC

/* Affected rows: 0  Found rows: 83,597  Warnings: 0  Duration for 1 query: 0.032 sec. (+ 0.531 sec. network) */

+----+-------------+---------+-------+---------------+---------+---------+-------+--------+--------------------------+
| id | select_type | table   | type  | possible_keys | key     | key_len | ref   | rows   | Extra                    |
+----+-------------+---------+-------+---------------+---------+---------+-------+--------+--------------------------+
|  1 | SIMPLE      | tag     | const | PRIMARY,tag   | tag     | 767     | const |      1 | Using index              |
|  1 | SIMPLE      | map_tag | index | NULL          | PRIMARY | 8       | NULL  | 888729 | Using where; Using index |
+----+-------------+---------+-------+---------------+---------+---------+-------+--------+--------------------------+

然后我自己加入地图,SQL变为:

SELECT 
    `map`.*
FROM (
    SELECT DISTINCT map_id 
    FROM `map_tag` 
    INNER JOIN `tag` USING (tag_id) 
    WHERE tag IN (...) 
    GROUP BY map_id HAVING COUNT(DISTINCT tag_id) = #
    ORDER BY map_id DESC
) matching 
INNER JOIN `map` USING (map_id)
INNER JOIN `map_tag` USING (map_id) 
INNER JOIN `tag` USING (tag_id) 
LIMIT 0, 10

/* Affected rows: 0  Found rows: 10  Warnings: 0  Duration for 1 query: 0.297 sec. */

+----+-------------+------------+--------+---------------+---------+---------+---------------------------+--------+--------------------------+
| id | select_type | table      | type   | possible_keys | key     | key_len | ref                       | rows   | Extra                    |
+----+-------------+------------+--------+---------------+---------+---------+---------------------------+--------+--------------------------+
|  1 | PRIMARY     | <derived2> | ALL    | NULL          | NULL    | NULL    | NULL                      |  83597 |                          |
|  1 | PRIMARY     | map        | eq_ref | PRIMARY       | PRIMARY | 4       | matching.map_id           |      1 |                          |
|  1 | PRIMARY     | map_tag    | ref    | PRIMARY       | PRIMARY | 4       | matching.map_id           |      2 | Using index              |
|  1 | PRIMARY     | tag        | eq_ref | PRIMARY       | PRIMARY | 4       | maps.local.map_tag.tag_id |      1 | Using index              |
|  2 | DERIVED     | tag        | const  | PRIMARY,tag   | tag     | 767     |                           |      1 | Using index              |
|  2 | DERIVED     | map_tag    | index  | NULL          | PRIMARY | 8       | NULL                      | 888729 | Using where; Using index |
+----+-------------+------------+--------+---------------+---------+---------+---------------------------+--------+--------------------------+

当我想要实际使用标签时,问题就出现了。

SELECT 
    `map`.*,
    GROUP_CONCAT(`tag`.tag) AS tags
FROM (
    SELECT DISTINCT map_id 
    FROM `map_tag` 
    INNER JOIN `tag` USING (tag_id) 
    WHERE tag IN (...) 
    GROUP BY map_id HAVING COUNT(DISTINCT tag_id) = #
    ORDER BY map_id DESC
) matching 
INNER JOIN `map` USING (map_id)
INNER JOIN `map_tag` USING (map_id) 
INNER JOIN `tag` USING (tag_id) 
GROUP BY map_id
LIMIT 0, 10

/* Affected rows: 0  Found rows: 10  Warnings: 0  Duration for 1 query: 47.641 sec. */

+----+-------------+------------+--------+---------------+---------+---------+---------------------------+--------+---------------------------------+
| id | select_type | table      | type   | possible_keys | key     | key_len | ref                       | rows   | Extra                           |
+----+-------------+------------+--------+---------------+---------+---------+---------------------------+--------+---------------------------------+
|  1 | PRIMARY     | <derived2> | ALL    | NULL          | NULL    | NULL    | NULL                      |  83597 | Using temporary; Using filesort |
|  1 | PRIMARY     | map        | eq_ref | PRIMARY       | PRIMARY | 4       | matching.map_id           |      1 |                                 |
|  1 | PRIMARY     | map_tag    | ref    | PRIMARY       | PRIMARY | 4       | matching.map_id           |      2 | Using index                     |
|  1 | PRIMARY     | tag        | eq_ref | PRIMARY       | PRIMARY | 4       | maps.local.map_tag.tag_id |      1 |                                 |
|  2 | DERIVED     | tag        | const  | PRIMARY,tag   | tag     | 767     |                           |      1 | Using index                     |
|  2 | DERIVED     | map_tag    | index  | NULL          | PRIMARY | 8       | NULL                      | 888729 | Using where; Using index        |
+----+-------------+------------+--------+---------------+---------+---------+---------------------------+--------+---------------------------------+

47秒查询,从INNER JOINmap表之前的0.3秒开始。子查询切换到使用临时和filesort,我不知道为什么。我在所有相关表中为map_id设置了索引,但出于某种原因,它在执行GROUP BY时不使用它们。 ORDER BY也会导致此行为。

我是否需要做些什么来改变表格以便使用索引?是否有更有效的方法将map表引入并获取所有标记,而不仅仅是匹配的标记?


目标是,如果有三个地图(这不表示表格结构,tagsmapmap_tagtag表关系) :

+-------+---------------+
| name  |     tags      |
+-------+---------------+
| map A | aaa, bbb, ccc |
| map B | bbb, ccc, zzz |
| map C | ccc, zzz, yyy |
+-------+---------------+

如果我搜索标签“bbb”和“ccc”,我会得到结果:

+-------+---------------+
| name  |     tags      |
+-------+---------------+
| map A | aaa, bbb, ccc |
| map B | bbb, ccc, zzz |
+-------+---------------+

所有标记属于每个地图,而不仅仅是匹配的标记,并且我能够按map列对结果map行进行排序,而无需MySQL忽略索引:

...
ORDER BY `map`.published DESC

/* Affected rows: 0  Found rows: 10  Warnings: 0  Duration for 1 query: 00:01:35 (+ 0.078 sec. network) */

1 个答案:

答案 0 :(得分:1)

不是真正理解你的问题,也不是对评论的回答是,但是......我会尝试以这种方式构建它...你的内部查询是来自map_tag和标签表的合格标签和组的连接在那里完成了不同的concat,其中包含按地图ID分组的计数。完成...现在,您可以加入到符合条件的地图表。

为了帮助索引优化,我可以建议以下索引

table       index
map_tag     ( map_id, tag_id )
tag         ( tag_id, tag )
map         ( map_id )

SELECT
      m.*,
      PreTags.allTags
   from
      ( SELECT 
              mt.map_id,
              GROUP_CONCAT(DISTINCT t.tag ORDER BY t.tag SEPARATOR ',') allTags
           FROM 
              map_tag mt
                 JOIN `tag` t
                    ON mt.tag_id = t.tag_id
           group by
              mt.map_id
           having 
              SUM( case when t.tag in (...) then 1 else 0 end ) > 1
           order by
              mt.map_id DESC ) PreTags
         JOIN map m
            ON PreTags.map_id = m.map_id
   limit 
      0, 10

这样,内部查询为你做了concat,并且在获取最终的地图条目时你不必在外面重新应用它...并且由于内部按map_id分组,你不会有来自内部查询的重复。

这是另一种选择我会对它的表现感到好奇。

SELECT
      m.*,
      FullTags.allTags
   from 
      ( SELECT
              Just10.map_id,
              GROUP_CONCAT(DISTINCT t.tag ORDER BY t.tag SEPARATOR ',') allTags
           from 
              ( SELECT mt.map_id
                   FROM map_tag mt
                   where mt.tag_id in ( select t.tag_id
                                           from `tag` t
                                           where t.tag in (...) )
                   group by mt.map_id
                   having COUNT(*) > 1
                   order by mt.map_id DESC
                   limit 0, 10 ) Just10
                 JOIN map_tag mt2
                    ON Just10.map_id = mt2.map_id
                    JOIN `tag` t
                       ON mt2.tag_id = t.tag_id
           group by
              Just10.map_id ) FullTags
      JOIN map m
         ON FullTags.map_id = m.map_id

对于那些具有多个匹配标记的人来说,最内部的查询最多只能获得10个条目。然后,只有那些10才会返回并获得group_concat() - 再次,这只是最多10条记录,然后最终加入以获取其余的地图数据。

相关问题