SQL 使用 3 个连接优化查询

SQL query optimization with 3 joins

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

每个 Table

的信息
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;

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

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

有一件事让我印象深刻。您的两个子查询中都有这种行。

          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

并在您的帖子和评论 table 中的 CreationDate 列中建立索引。然后查询规划器可以在索引中随机查找到第一个匹配行,然后顺序读取它直到最后一个匹配行。这称为索引范围扫描,比完整 table 扫描更有效。

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 table 中获取用户名,就像这样。

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/