【问题标题】:Optimization strategy for "get most recent comments from all posts"“获取所有帖子的最新评论”的优化策略
【发布时间】:2014-01-09 21:14:40
【问题描述】:

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

一共有三个表,MySQL myisam:

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

这就是我获取用户帖子上人们最近制作的 cmets 的方式:

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 万个 cmets 和大约 50 万用户的数据库。应该应用什么样的索引策略?有没有更好的方法来编写查询?是否有可能让查询在几秒钟内返回结果?这似乎取决于用户的帖子数量。

非常感谢。

【问题讨论】:

  • 您知道,当您达到这种规模时,可以得到一些专业支持。
  • @SamD 这是一个 AWS db.m1.small RDS 实例:1.7 GB 内存、1 个 ECU(1 个虚拟内核和 1 个 ECU)、64 位平台、中等 I/O 容量。所有的表都在使用 myisam。
  • 如果您指定INNER JOIN 而不仅仅是JOIN,会发生什么?这应该至少会加快一点速度。另外,您目前在这三个表上有哪些索引?您上次分析这些表格是什么时候?
  • 请提供EXPLAIN ... 的输出以及您引用的3 个表的create table 语句。
  • @dg99 显式使用 INNER JOIN 并没有什么不同。从我在 EXPLAIN 中看到的内容来看,我的索引应该没问题,但是 post.author_fk、comment.author_fk、comment.post_fk、(comment.post_fk、comment.author_fk)、user.id 有一个。也许你可以分享你对如何索引这个的想法?该表是定期分析的,所以那里没问题

标签: mysql


【解决方案1】:

跟进

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

“太小”的 InnoDB 缓冲池会导致性能问题,尤其是当并发查询竞争池中的块并导致磁盘读取时。 (正如我之前提到的,“使用文件排序”操作可能很昂贵(就资源和时间而言)。

鉴于大量的行和性能要求,我的目标是访问单个索引并避免“使用文件排序”操作的查询计划。

此时,我希望对数据模型进行非规范化以提高性能,以便拥有适当的索引。

鉴于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 行的限制,我将从一个查询开始,该查询可以快速从感兴趣的 cmets 中获取行,使用索引扫描。

   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

该查询应该能够通过对新索引使用降序范围扫描来避免“使用文件排序”操作。理想情况下,我们应该有一个覆盖索引,但考虑到我们需要返回text 列,维护一个更大的索引可能会更昂贵。一个我验证了这个查询表现良好,我将它包装在括号中,并将其作为内联视图包含在另一个查询中,例如:

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,或类似的升序值,其中最新的 cmets 具有“更高”的 id 值。)

以下是我对索引和查询计划进行调整的方法。

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

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

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

(如果这是 InnoDB,并且这被定义为 FOREIGN KEY 约束,那么适当的索引将已经存在)。如果 post 中的行相当“大”,而“title”列通常相当小,那么我倾向于将该列也包含在索引中,这样我们就有了一个覆盖索引。 (当从索引满足查询而不引用基础表中的页面时,EXPLAIN 输出将在 Extra 列中显示“'Using index'”,这可以显着提高性能。)

由于查询还引用了 id 列,因此可能还需要包含该列,但如果这是 PRIMARY KEY(集群键),则该列值实际上可能已经包含在索引中,作为“指针" 返回表格中的行。

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

由于连接到comment 表的谓词位于post_fk 列上,我们可能希望 该列前导的索引。如果text 列占据了注释表中的绝大部分空间,那么为该表创建一个覆盖索引也可能是有益的,尽管这可能不会对性能有多大好处。

... ON comment (post_fk)

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

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

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

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

user 表的连接谓词已经在主键上。

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

【讨论】:

  • 非常感谢您的回复,很抱歉这么晚才回复。在深入研究之后,确实是排序造成了所有这些痛苦。我有你建议的所有索引,当我删除“ORDER BY comment.id DESC”时查询非常快,但我找不到排序方法并让查询快速。不幸的是,我无法使用 PHP 进行排序和限制,因为如果返回数以万计的行,它会占用太多的 RAM...
  • 我读到 ORDER BY 使用“文件排序”和“临时”,当没有关于您订购内容的索引时。那么,当我按主索引comment.id 排序时,为什么必须使用'filesort/temporary'?我猜这与这里发生的加入有关....
  • 我尝试按照您的建议添加日期谓词。它大大提高了查询时间,但仍然太慢。给您一个想法,当此查询必须对 15k 多行进行排序时(即,用户已将 15k+ cmets 添加到他的帖子中),在没有 date 子句的情况下运行它可能需要 30 多秒。将此限制在上周添加的 cmets 可将执行时间减少 10 到 20 秒,无论是给予还是接受。如果有数百名用户访问此查询,这会更好,但不能接受。
猜你喜欢
  • 2015-10-14
  • 2016-01-02
  • 1970-01-01
  • 2012-04-12
  • 2018-08-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多