【问题标题】:SQL query optimization with 3 joins使用 3 个连接进行 SQL 查询优化
【发布时间】:2020-08-08 01:09:01
【问题描述】:

我正在学习关于执行时间的查询优化行为。我有 3 个表,分别是帖子、cmets 和用户。通过返回在 2010 年发表的 cmets 数超过帖子数的用户及其计数,以下查询完美运行。我相信它可以被优化,我会很感激带有解释的优化代码。

每个表的信息

  • 用户表;有 40325 行,在其主键 (Id) 上有索引。
  • 帖子表;有 91986 行,在其主索引上 key(PostId) 以及 OwnerUserId 是外键 引用用户表。
  • 评论表 - 有 174305 行,在其主键 UserId(用于用户表)和 PostId(用于帖子表)上建立索引。
SELECT pos_table.user_ID, pos_table.Username, comms, pos from
    (SELECT
    users.Id as 'user_ID', users.DisplayName as 'Username', count(posts.Id) as pos
    FROM
    users
    INNER JOIN posts ON posts.OwnerUserId = users.Id
    WHERE YEAR(posts.CreationDate) = 2010
    group by users.Id
    ) pos_table
    JOIN
    (SELECT
    users.Id as 'user_ID', users.DisplayName as 'Username', count(comments.Id) as
    comms
    FROM
    users
    INNER JOIN comments ON comments.UserId = users.Id
    WHERE YEAR(comments.CreationDate) = 2010
    group by users.Id
    ) comms_table
    on pos_table.user_ID = comms_table.user_ID
    HAVING comms > pos
    order by user_ID
    limit 50;

在我上面的查询中,有两个子查询。一个用于帖子,另一个用于 cmets。我想看看如何优化它以减少执行时间。

上述查询的结果,以及我的 EXPLAIN 查询的附件:

【问题讨论】:

  • 我们需要更多信息来帮助您优化这一点。请阅读meta.stackoverflow.com/a/271056 并特别注意查询优化部分。那么请edit您的问题向我们展示表定义和查询计划。
  • 执行命令 EXPLAIN 与您一起查询与我们共享的输出
  • 请原谅,这是我的第一个问题。如果我错过了任何重要的细节,请告诉我。谢谢
  • 您编辑的问题看起来不错。查询性能是一项棘手的工作,您拥有的信息越多越好。

标签: mysql sql query-optimization database-performance query-performance


【解决方案1】:

有一件事突然出现在我身上。您的两个子查询中都有这种行。

          WHERE YEAR(posts.CreationDate) = 2010

您在列值上调用函数。那不是sargeable。它可以防止 MySQL 能够利用该列上的索引,而是需要完全扫描。 (MySQL 和其他 DBMS 仍然愚蠢到不知道 YEAR(timestamp) 可以满足索引范围扫描。)

所以把那些 WHERE 改成这种东西。

          WHERE posts.CreationDate >= '2010-01-01'
            AND posts.CreationDate <  '2010-01-01' + INTERVAL 1 YEAR

并在您的帖子和 cmets 表中的 CreationDate 列上放置索引。然后查询计划器可以在索引中随机查找第一个匹配行,然后顺序读取它,直到最后一个匹配行。这称为索引范围扫描,比全表扫描效率更高。

EDIT您需要以下索引:

CREATE INDEX date_user ON posts ( CreationDate, OwnerUserId );
CREATE INDEX date_user ON comments ( CreationDate, UserID);

我建议您重构您的查询,以使您的子查询(所有工作发生的地方)更快。

这些应该是子查询。它们各自在所需的时间范围内为每个用户生成多个项目。

   SELECT OwnerUserId, COUNT(*) posts
     FROM posts
    WHERE CreationDate >= '2010-01-01'
      AND CreationDate <  '2010-01-01' + INTERVAL 1 YEAR
    GROUP BY OwnerUserId

   SELECT UserId, COUNT(*) comments
     FROM comments
    WHERE CreationDate >= '2010-01-01'
      AND CreationDate <  '2010-01-01' + INTERVAL 1 YEAR
    GROUP BY UserId

这些查询通过仅聚合(按组汇总)满足查询所需的最少量数据来节省时间。而且,他们可以通过对我建议的索引进行快速索引范围扫描来满足他们。

然后,您可以在主查询中使用这些子查询,从users 表中提取用户名,如下所示。

SELECT users.Id user_ID, users.Username, c.comments, p.posts
  FROM users
  JOIN (
       SELECT OwnerUserId, COUNT(*) posts
         FROM posts
        WHERE CreationDate >= '2010-01-01'
          AND CreationDate <  '2010-01-01' + INTERVAL 1 YEAR
        GROUP BY OwnerUserId
        ) p ON users.ID = p.OwnerUserId
   JOIN (
       SELECT UserId, COUNT(*) comments
         FROM comments
        WHERE CreationDate >= '2010-01-01'
          AND CreationDate <  '2010-01-01' + INTERVAL 1 YEAR
        GROUP BY UserId
        ) c ON users.ID = c.UserId
  WHERE c.comments > p.posts
  ORDER BY users.ID
  LIMIT 50;

我怀疑如果您添加我提到的复合索引,您将获得很大的性能提升。您可以删除 CreationDate 上的索引;添加复合索引时它们是多余的。

这里值得参考https://use-the-index-luke.com/

【讨论】:

  • 谢谢 O.Jones。我在两个表上都添加了索引。我也更新了我的问题,谢谢。 P.S:这是 stackoverflow 数据集。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-09-26
  • 2012-08-16
  • 1970-01-01
  • 2013-02-05
  • 2016-04-15
  • 2016-10-16
相关资源
最近更新 更多