在为用户创建的游戏地图的存档制作标签表时,用于获取包含所有提供的标签的地图的地图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 JOIN
到map
表之前的0.3秒开始。子查询切换到使用临时和filesort,我不知道为什么。我在所有相关表中为map_id
设置了索引,但出于某种原因,它在执行GROUP BY
时不使用它们。 ORDER BY
也会导致此行为。
我是否需要做些什么来改变表格以便使用索引?是否有更有效的方法将map
表引入并获取所有标记,而不仅仅是匹配的标记?
目标是,如果有三个地图(这不表示表格结构,tags
是map
到map_tag
到tag
表关系) :
+-------+---------------+
| 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) */
答案 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条记录,然后最终加入以获取其余的地图数据。