“获取所有帖子的最新评论”的优化策略

时间:2013-12-20 22:58:49

标签: mysql

此查询的目的是获取其他用户在某人帖子上发表的最新评论。它应该包含评论的用户的名称,他们评论的帖子的标题以及实际的评论文本。

有三个表,MySQL myisam:

comment: id, author_fk, post_fk, text, date, ...
post: id, author_fk, content, date, ...
user: id, name, ...

这就是我收到用户帖子上最新评论的方式:

SELECT comment.text, user.name, post.title 
FROM comments
JOIN user ON user.id = comment.author_fk
JOIN post ON post.id = comment.post_fk
WHERE post.author_fk = [id of the user who posted content] 
ORDER BY comment.id
LIMIT 20

以下是我在执行此操作时引用的主题:mysql/php: show posts and for each post all comments

问题是这真的很慢。我正在使用一个拥有超过200万个帖子,1500万条评论和大约500万用户的数据库。应该采用什么样的索引策略?有没有更好的方法来编写查询?是否有可能让查询在几秒钟内返回结果?这似乎有多慢或多快取决于用户的帖子数量。

非常感谢。

1 个答案:

答案 0 :(得分:0)

<强>后续

我注意到在问题发布后2个月,OP增加了评论。我将拒绝重复我在原始答案(下面保留)中提出的观点,例如使用EXPLAIN,创建适当的覆盖索引,以及向查询添加谓词。鉴于OP已经验证了这一点。

InnoDB缓冲池太小了#34;将导致性能问题,特别是如果有并发查询竞争池中的块,并导致磁盘读取。 (正如我之前提到的,&#34;使用filesort&#34;操作可能很昂贵(在资源和时间方面。)

考虑到大量的行和性能要求,我的目标是一个访问单个索引的查询计划,并避免使用&#34;使用filesort&#34;操作

此时,我正在寻求对数据模型进行非规范化以提高性能,以便我可以获得适当的索引。

鉴于post.author_fk上有一个等同谓词,并且({有效)comment.id上有一个降序范围扫描,我会考虑将这两列放到一个列中索引。

这意味着我要将post.author_fk的值添加为comments表中的列。

ALTER TABLE comments ADD post_author ...

当然,需要修改对comments表执行INSERT / UPDATE的代码,以维护此列。 (更新整个表格会很痛苦,考虑到行数,我不会一举尝试。如果我必须这样做,我会在较小的一串中解决这个问题。

接下来,我在该表上添加一个索引:

CREATE INDEX comments_IX2 ON comments (post_author, id)  

然后,我得到一个查询,将其用作覆盖索引,并尽快应用LIMIT 20。我们非常谨慎地介绍内联视图(因为它们可能是性能杀手),但在这种情况下,如果限制为20行,我会从一个查询开始,该查询可以快速获取感兴趣的评论中的行,使用索引扫描。

   SELECT c.id
        , c.post_fk
        , c.author_fk
        , c.text
     FROM comments c
    WHERE c.post_author = [id of the user who posted content]
    ORDER BY c.post_author DESC, c.id DESC
   LIMIT 20

该查询应该能够避免&#34;使用filesort&#34;通过对新索引使用降序扫描来进行操作。理想情况下,我们有覆盖索引,但考虑到我们需要返回text列,维护更大的索引可能会更昂贵。我验证了这个查询运行良好,我将其包装在parens中,并将其作为内联视图包含在另一个查询中,如:

SELECT c.text
     , u.name
     , p.title 
  FROM ( SELECT c.id                                                 
              , c.post_fk
              , c.author_fk
              , c.text
           FROM comments c
          WHERE c.post_author = [id of the user who posted content]  -- new col 
          ORDER BY c.post_author DESC, c.id DESC                     -- new col
          LIMIT 20
       ) t
  JOIN user u
    ON u.id = t.author_fk
  JOIN post p
    ON p.id = t.post_fk
 ORDER BY t.id DESC

原始答案:如果查询返回了您想要的结果

(我怀疑你的意思是ORDER BY comment.id DESC,假设id列是AUTO_INCREMENT,或者类似的升序值,其中最新的评论具有&#34;更高的&#34; id值。)

以下是我如何调整索引和查询计划。

首先,最重要的是,使用EXPLAIN来获取MySQL当前正在使用的查询计划。

其次,验证统计信息是最新的......在每个表上使用ANALYZE TABLE

查看查询,看起来我们肯定想要一些索引。我们希望在post表上使用author_fk作为前导列的索引,因为它是一个等式谓词,并且我们期望一些非常好的基数(我们期望这个谓词)通常会消除大量的行,并返回少于10%的行。

(如果这是InnoDB,并且这被定义为FOREIGN KEY约束,则已经存在适当的索引)。如果帖子中的行是公平的&#34;大&#34;,那么&#34;标题&#34;列通常相当小,然后我也倾向于在索引中包含该列,以便我们有覆盖索引。 (当索引满足查询而不引用基础表中的页面时,EXPLAIN输出将在'Using index'列中显示&#34; Extra&#34;这可以加快性能显著。)

由于查询还引用了id列,因此可能还需要包含该列,但如果它是PRIMARY KEY(群集密钥),则该列值实际上可能已包含在index,作为&#34;指针&#34;回到表格中的那一行。

... ON post (author_fk, title, id)

由于comment表的加入谓词位于post_fk列,我们可能会想要 该列前导的索引。如果text列占用注释表中的绝大部分空间,那么为此表创建覆盖索引可能也是有益的,但这可能不会对性能有多大好处。

... ON comment (post_fk)

我认为查询计划中最大的性能杀手就是排序操作。 (EXPLAIN输出可能会在'Using filesort'列中显示Extra。)

MySQL必须从满足谓词的comment表中检索每一行,然后对该组行执行排序操作。

如果您可以添加其他一些谓词,例如您不希望评论日期超过7天,或类似的东西,则会减少需要排序的行数。

LIMIT子句几乎在查询计划中应用。因此,即使您只要求20行,MySQL实际上可能会排序成千上万行。

user表的连接谓词已经在看起来像是主键。

(我假设id列被定义为每个表的PRIMARY KEY的规范模式。)