【问题标题】:Optimizing Inner Join Queries优化内连接查询
【发布时间】:2021-04-22 07:25:56
【问题描述】:

我有这个查询,我想知道我是否可以通过某种方式对其进行优化,因为目前执行需要很长时间(比如 4/5 秒)

SELECT *
FROM `posts` ml INNER JOIN
     posts_tag_one gt
     ON gt.post_id = ml.id AND gt.tag_id = 15 INNER JOIN 
     posts_tag_two gg
     ON gg.post_id = ml.id AND gg.tag_id = 5
WHERE active = '1' AND NOT ml.id = '639474'
ORDER BY ml.id DESC
LIMIT 5

我想说它有 600k+ 个帖子的数据库,posts_tag_one 500 万条记录,posts_tag_two 475k+ 条记录。

我给出的那个例子只有 2 个连接,但在某些情况下,我最多有 4 个连接,所以其他表有 300k-400k 条记录。

我正在为 posts_tag_one、posts_tag_two 表使用外键和索引,但查询仍然很慢。

任何建议都会有所帮助。谢谢!

【问题讨论】:

  • 这些表上的索引是什么,如果有的话?还提供explain结果
  • 确认“active = 1”关联的是哪个表?含义是帖子表(ml 别名)
  • 欢迎来到 SO。请参阅Why should I provide an MCRE for what seems to me to be a very simple SQL query - 并且仅选择(并正确限定)您实际想要返回的列。
  • 此外,关于查询性能的问题总是需要给定查询的 EXPLAIN

标签: mysql sql optimization inner-join


【解决方案1】:

通过传递属性(如果 a=b 且 b=c,则 a=c),您的 ML.ID = GT.Post_ID = GG.Post_ID。由于您正在尝试对特定标签进行预限定,因此我将重写并尝试通过移动到前面位置并使用更好的索引来优化查询来查看数据的基数是否会有所帮助。此外,MySQL 有一个很好的关键字“STRAIGHT_JOIN”,它告诉引擎按照我告诉你的顺序查询数据,不要为我考虑。我用了很多次,都看到了明显的改善。

SELECT STRAIGHT_JOIN
        *
    FROM 
        posts_tag_two gg
            INNER JOIN posts_tag_one gt
                ON gg.post_id = gt.post_id
                AND gt.tag_id = 15
                INNER JOIN posts ml 
                    ON gt.post_id = ml.id
                    AND ml.active = 1
    WHERE 
            gg.tag_id = 5
        AND NOT gg.post_id = 639474
    ORDER BY
        gg.post_id DESC
    LIMIT 5

我会确保下表/多字段索引

table            index
Posts_Tag_One    ( tag_id, post_id )
Posts_Tag_Two    ( tag_id, post_id )
posts            ( id, active )

从您为 tag_id = 5 预过滤的 Posts_Tag_Two 表开始,您已经将列表缩减为那些预限定的 FIRST。不是从所有帖子开始,然后查看符合标签的帖子。

第二级联接是到具有相同 ID 的 POSTS_TAG_ONE 表,但该级别由其 Tag_ID = 15 过滤。

只有这样,它才会关心进入 POSTS 表进行活动。

由于顺序基于 ID 降序,并且 Posts_tag_two 表“post_id”的值与 Posts.id 相同,posts_tag_two 表中的索引应该返回已经预排序的记录。

HTH,并且有兴趣了解最终的性能差异。同样,我多次使用 STRAIGHT_JOIN 并显着提高了性能。我通常也不对所有表/所有列执行“选择 *”。得到你需要的东西。

反馈

@eshirvana,在很多情况下,是的,优化器默认会这样做。但有时,设计师更了解数据的构成。让我们以 POSTS 处于领先位置的场景为例。你有一个房间的帖子盒子。每个盒子包含 10k 条记录。您必须遍历所有 10k 记录,然后到下一个框,直到您通过 400k 记录...再次,仅举个例子。找到这些后,它会根据特定标签的过滤条件进行连接。这些也是按 ID 排序的,因此您必须进行一对一的关联。那么哪个表保持在主要位置。

现在,按标签的索引和posts_tag 表之一(选择较小的是#2)。 现在,你有一个盒子房间,但每个盒子里面只有一个标签。如果您有 300 个标签 ID 可用,那么您已经删除了 x 条记录,只为您提供了您通过资格预审的小样本。

所以现在,第二张帖子表同样是一个盒子房间。他们的盒子也按标签分类。所以现在你只需要抓住标签 #15 的盒子。

所以现在您有两组非常有限的记录,JOIN 可以匹配两种情况下都存在的 ID。只有完成此操作后,您才需要转到帖子表,通过 ID 将是快速和直接的。但是在索引中处于活动状态,引擎永远不需要去任何实际的数据页面来检索数据,直到满足所有条件。只有这样,它才会从返回的 3 个相应的表中提取记录。

【讨论】:

  • (id,active) 没用。
  • 这不是优化器默认做的最简单的事情吗?
  • @eshirvana,请参阅我的反馈回复以帮助澄清。我已经看到将 20 多万条记录连接到 22 个不同的查找表的查询从 24 小时后阻塞系统到在不到 2.5 小时内完成。
  • @DRapp 感谢您的解释和帮助!我很感激!
【解决方案2】:

听起来posts_tags 是一个多对多映射表?它需要两个索引:(post_id, tag_id)(tag_id, post_id)。其中之一可能应该是PRIMARY KEY(拥有 auto_increment id 是一种浪费并且会减慢速度)。另一个应该是INDEX(不是UNIQUE)。更多讨论:http://mysql.rjweb.org/doc.php/index_cookbook_mysql#many_to_many_mapping_table

但是,为什么posts_tag_twoposts_tag_one 都有呢?

除了那些“复合”键之外,不要还有单列 (post_id)(tag_id)

如果tag 只是一个短字符串,不要费心对其进行规范化;只需将其放在表中即可。

如需进一步讨论,请为每张桌子提供SHOW CREATE TABLE。还有EXPLAIN SELECT ...

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-03-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-24
    • 2020-04-30
    • 1970-01-01
    相关资源
    最近更新 更多