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

MySQL性能,当查询使用order by/group by时,子查询使用临时文件排序,mysql,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

在为用户创建的游戏地图存档制作标记表时,用于获取包含所有提供标记的地图的地图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 |
+----+-------------+------------+--------+---------------+---------+---------+---------------------------+--------+--------------------------+
如果我搜索标签“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) */

然而,并没有真正理解你的问题,也没有回答你的评论。。。我会尝试用这种方式来组织它。。。您的内部查询是一个来自map_标记和符合条件的标记上的标记表的联接,distinct的组concat在那里完成,其计数按map 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个条目,这些条目包含多个与您正在查找的应用order by的标记匹配的条目。然后,仅针对这10条记录返回并获取组_concat()——同样,这只针对最多10条记录,然后最终加入以获取其余的地图数据。

您能描述一下您希望查询执行的操作吗?当然。最终结果是从
map
表中获取整行,其中包含
GROUP\u CONCAT
tags字段,该字段包含通过
map\u标记和
tag
表通过
internal JOIN
s获得的属于map的标记。我能看懂这个问题。我不理解结果集的用途。结果集是从整个
map
表中筛选出的结果,添加了属于该行的所有标记,并通过提供的标记进行筛选。目标是能够放入标记,然后取出带有这些标记的图。我希望属于这些映射的所有标记都在它们的行中,因此我不需要在原始查询的顶部查询每个标记。如果我不清楚,我很抱歉。谢谢你的回答,这个解决方案的问题是
preTags
只包含
中提供的标记和(…)中的t.tag
。它不考虑可能属于地图的任何其他标记。如果地图上有标签“aaa”、“bbb”和“ccc”,我在“aaa”、“bbb”中搜索
和t.tag
,它将返回
PreTags.allTags
作为“aaa、bbb”而不是“aaa、bbb、ccc”,如果这是我想要实现的目标,那么它将返回
PreTags.allTags
。。。我已经修改了inner的查询以应用HAVING,但将group_concat过程中的所有标记作为默认值获取答案用于获取行中的连接标记,但它遇到了与我最初方法相同的性能问题。一旦我为
map
表中的一列输入了
orderby
,子查询就会转到临时文件排序并减慢速度。1.7秒用于查询解决方案,无需按
map
列对结果进行排序,16.5秒用于排序。显然比我之前的47秒进步了一步,但我想知道为什么MySQL只有在
orderby
groupby
出现时才使用索引。@sidke,它在索引上失败,因为限定符来自您正在查找的另一个表中的特定标记值,而不是map_tags表中的索引的一部分,以利用索引的有效限定/排序方式。它必须先抓取每一条带有计数的记录,然后排序。@sidke,我用另一个可能的替代查询修改了答案,不知道这个查询将如何满足您的需要。
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
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