慢 MYSQL 查询,需要帮助理解索引

Slow MYSQL Queries, need help understanding indexes

出于此 post 的目的,我已将我的问题简化为最纯粹的形式。 我有 3 个 table:游戏,games_tags 和 games_tags_map

如果我想为每个游戏获取 table 个标签,我会这样做:

SELECT `games_tags_map`.`game_id` as 'game_id', GROUP_CONCAT(`games_tags_map`.`tag_id`) as 'tags'
FROM `games_tags_map`
LEFT JOIN `games_tags` on `games_tags`.id = `games_tags_map`.`tag_id`
GROUP BY `games_tags_map`.game_id

大约需要 1 毫秒

SELECT `games`.`id` AS 'id' from `games`

这需要 <1 毫秒。

然而,当我尝试加入这两个时:

SELECT `games`.`id` AS 'id',
t.`tags` as `tags`
FROM `games`
LEFT JOIN (
    SELECT `games_tags_map`.`game_id` as 'game_id', GROUP_CONCAT(`games_tags_map`.`tag_id`) as 'tags'
    FROM `games_tags_map`
    LEFT JOIN `games_tags` on `games_tags`.id = `games_tags_map`.`tag_id`
    GROUP BY `games_tags_map`.game_id
) t ON t.`game_id`=`games`.`id`

大约需要 100 毫秒

但是,当我执行等效查询时:

SELECT `games`.`id` AS 'id',
GROUP_CONCAT(DISTINCT `games_tags`.`tag`) AS 'tags'
FROM `games`
LEFT JOIN `games_tags_map` ON `games`.`id` = `games_tags_map`.`game_id`
LEFT JOIN `games_tags` ON `games_tags`.`id` = `games_tags_map`.`tag_id`
WHERE `games`.`active`=1
GROUP BY `games`.`id`

需要2毫秒... 但是,当我需要按主列 (id) 以外的任何其他方式对其进行排序时,需要 ~80ms

澄清一下,这是我实际数据库的一个非常简化的版本,它的加载时间要长得多,并导致我的网站出现问题,但问题出在这些查询中。

我的数据库针对如此巨大不同的加载时间设置的方式显然存在缺陷。我试过添加更多索引,但没有帮助。 在 table 'games' 我有主索引 'id' 在 table 'games_authors_map' 上,由 'game_id' 和 'author_id'

组成的主索引

我知道有问题,但我无法解决,我不明白为什么。

请帮忙。

尝试对表中的外键使用索引(games_tags_map.tag_idgames_tags_map.game_id)并且还索引您从中获取的列试图对查询进行排序 这将解决您的问题。

与其对所有游戏标签 table 进行连接(这本身就可以),为什么不在主游戏 table 中添加一个聚合列,所有标签都放在前面,这样你不需要加入。然后,您可以简单地添加一个触发器,每当从 game_tags_map table 添加或删除标签时,它都会更新主游戏 table。如果这只是为了显示给基于网络的游戏站点,那很好。如果某人对某种类型的游戏感兴趣,那么针对 game_tags_map table 的查询将很好地总结出该特定兴趣的列表。

您每次都在查询所有游戏,所以这可能是您更好的途径。

首先,查看您的第一个查询并删除勾号, 将您的长 table 名称分别引号和别名为 gtm 和 gt, 您的查询甚至从未使用 games_tags table 因为它是左连接 并且不使用其中的任何列...

SELECT 
      gtm.game_id, 
      GROUP_CONCAT(gtm.tag_id) as tags
   FROM 
      games_tags_map gtm
         LEFT JOIN games_tags gt 
            on gtm.tag_id = gt.id
   GROUP BY 
      gtm.game_id

所以本质上,它只是在做

SELECT 
      gtm.game_id, 
      GROUP_CONCAT(gtm.tag_id) as tags
   FROM 
      games_tags_map gtm
   GROUP BY 
      gtm.game_id

除非,您打算 group_concat() 显示文字描述 ID 表示而不是 ID 本身。如果按 ID,则 您的第二个查询还可以删除 games_tags table.

的内部左连接
SELECT 
      g.id AS id,
      t.tags as tags
   FROM 
      games g
         LEFT JOIN ( SELECT 
                          gtm.game_id, 
                          GROUP_CONCAT(gtm.tag_id) as tags
                       FROM 
                          games_tags_map gtm
                             LEFT JOIN games_tags 
                                on gtm.tag_id = gt.id
                       GROUP BY gtm.game_id ) t 
            ON g.id = t.game_id

在你最后的查询中,你是左连接实际得到标签描述 而不是标签。

SELECT 
      g.id,
      GROUP_CONCAT(DISTINCT gt.tag) AS tags
   FROM 
      games g
         LEFT JOIN games_tags_map gtm 
            ON g.id = gtm.game_id
            LEFT JOIN games_tags gt
               ON gtm.tag_id = gt.id 
   WHERE 
      g.active = 1
   GROUP BY 
      g.id

为了优化这个查询,我会提供以下索引..
这将使整个查询与覆盖索引一起使用并且可以处理 通过索引进行整个查询,永远不需要访问原始基础数据。

table           index
games           ( active, id )
games_tags_map  ( game_id, tag_id )
games_tags      ( id, tag )

最后一点,在尝试向 post 提供更多详细信息时,您可以 始终编​​辑您现有的 post,添加更多详细信息,然后向用户发送评论 关于提供的额外数据以供审查并可能提供额外的 内容/回答/回应。