【问题标题】:MySQL: combination of LEFT JOIN and ORDER BY is slowMySQL:LEFT JOIN 和 ORDER BY 的组合很慢
【发布时间】:2016-08-08 05:54:51
【问题描述】:

有两个表:posts(~5,000,000 行)和relations(~8,000 行)。

posts 列:

-------------------------------------------------
|  id  |  source_id  |  content  |  date (int)  |
-------------------------------------------------

relations 列:

---------------------------
|  source_id  |  user_id  |
---------------------------

我编写了一个 MySQL 查询posts 获取与特定用户相关的 10 条最新行

SELECT      p.id, p.content
FROM        posts AS p
LEFT JOIN   relations AS r
ON          r.source_id = p.source_id
WHERE       r.user_id = 1
ORDER BY    p.date DESC
LIMIT       10

但是,执行它需要大约 30 秒。

我已经在relations(source_id, user_id)(user_id)(source_id)(date)(date, source_id)posts 有索引。

EXPLAIN 结果:

如何优化查询?

【问题讨论】:

  • 请注意,这呈现为内部连接!
  • 尝试用 AND 替换 WHERE。
  • 请发布“EXPLAIN SELECT ...”的结果
  • 再想一想,您可能需要一个单独的 r.user_id 索引并选择 FROM 关系和 LEFT JOIN 帖子
  • @JanPapenbrock 将 WHERE 替换为 AND 返回与用户无关的结果。在帖子中添加了 EXPLAIN 结果。

标签: mysql join left-join query-performance


【解决方案1】:

试试这个

    SELECT p.id, p.content FROM posts AS p
    WHERE p.source_id IN (SELECT source_id FROM relations WHERE user_id = 1)
    ORDER BY  p.date DESC
    LIMIT       10

【讨论】:

  • 执行它需要更长的时间——60 秒。
  • 子查询总是比连接慢。我认为它很慢,因为它可能包含重复的行。无论如何..谢谢
【解决方案2】:

您可以在帖子表的日期列上放置一个索引,我相信这将有助于提高排序速度。

您还可以尝试在使用一些额外的 where 语句进行排序之前减少结果数量。例如,如果您知道今天可能有 10 条记录具有正确的 user_id,您可以将日期限制在今天(或 N 天前,具体取决于您的实际数据)。

【讨论】:

  • 抱歉,忘了提及 - 在posts 已经有一个date 的索引。我明白你的意思,但是,没有其他限制 - 日期可以是任何日期。
  • 这篇文章谈到了一些问题:percona.com/blog/2006/09/01/…。作者提到,过滤(user_id)和排序(日期)列上的多列索引一起可以产生比单独使用更好的结果。
  • 您可能想到的是source_id,而不是user_id,因为它在另一个表中。我为(source_id, date)添加了一个索引,但是性能仍然很差。
  • 哎呀!当您在深夜使用 Stack Overflow 时会发生这种情况。我实际上以为他们在同一张桌子上。这给了我一个想法:如果 user_id 和 source_id 是一对一的关系,您将能够修改您的架构以将 user_id 与 source_id 切换。或者,您可以对这两个表进行非规范化(请参阅dba.stackexchange.com/questions/4622/…)。我认为这取决于您希望此查询运行得更快的程度。希望这会有所帮助!
【解决方案3】:

我会考虑以下几点:-

首先,您只需要与用户相关的帖子中最近的 10 行。所以,INNER JOIN 应该就可以了。

SELECT      p.id, p.content
FROM        posts AS p
JOIN        relations AS r
ON          r.source_id = p.source_id
WHERE       r.user_id = 1
ORDER BY    p.date DESC
LIMIT       10

如果要获取没有relations 映射的记录,则需要LEFT JOIN。因此,执行LEFT JOIN 会导致对左表进行全表扫描,根据您的信息,该表包含约 5,000,000 行。这可能是您查询的根本原因。

为了进一步优化,考虑将WHERE 子句移到ON 子句中。

SELECT      p.id, p.content
FROM        posts AS p
JOIN        relations AS r
ON          (r.source_id = p.source_id AND r.user_id = 1)
ORDER BY    p.date DESC
LIMIT       10

【讨论】:

  • 我尝试了你的两个建议,但是仍然执行了大约 30 秒。
  • 哇.. 这真是令人惊讶。我对复合索引有点不确定。由于复合索引,我曾经遇到过严重的性能下降。所以,我会盲目地尝试在relations(date, source_id)posts 删除(source_id, user_id) 的复合索引。
  • 我也尝试了不同的索引变体,但这些都没有任何效果。
  • 我想到的唯一另一件事可能是您的查询是否像“批处理”一样每 30 秒执行一次?你能看看explain 计划是否有任何改进吗?
  • EXPLAIN 你的查询是完全一样的。没有 batch 的东西,因为如果我删除排序 - 它快如闪电。查询在某些时候完全是错误的。
【解决方案4】:

您的 WHERE 子句将您的外连接呈现为纯粹的内连接(因为在外连接的伪记录中 user_id 将始终为空,从不为 1)。

如果你真的希望这是一个外部连接,那么它完全是多余的,因为posts 中的每条 记录当然在relations 中要么有匹配,要么没有匹配。您的查询将是

select id, content 
from posts 
order by "date" desc limit 10;

如果你真的不希望这是一个外连接,但想要relations 中匹配,那么我们讨论的是表中的存在,EXISTS 或 @987654327因此@子句:

select id, content
from posts
where source_id in
(
  select source_id
  from relations
  where user_id = 1
)
order by "date" desc
limit 10;

relations(user_id, source_id) 上应该有一个索引 - 按此顺序,因此我们可以先选择 user_id 1 并获取所有需要的 source_id 的数组,然后查找。

当然,您还需要一个您可能已经拥有的posts(source_id) 上的索引,因为source_id 是一个ID。您甚至可以使用复合索引 posts(source_id, date, id, content) 加快处理速度,因此不必再读取表本身 - 所需的所有信息都已在索引中。

更新:这是相关的EXISTS 查询:

select id, content
from posts p
where exists
(
  select *
  from relations r
  where r.user_id = 1
  and r.source_id = p.source_id
)
order by "date" desc
limit 10;

【讨论】:

  • 谢谢你,但是我之前有过这个答案:stackoverflow.com/a/36661096/1696898 - 对性能没有影响。
  • 哦,我没看到这个。所以你确实提到了索引,但查询仍然很慢?我认为这是 MySQL 中的一个重大缺陷。您可能使用的是旧版本吗?我将添加相关的EXISTS 查询,也许MySQL 与IN 有问题。
  • 是的,我尝试了您建议的索引。与EXISTS 相同。 MySQL 版本 – 最新版本,SSD 服务器。我通过删除order by 进行了实验——效果很快。如果我删除user_id=1,也可以快速工作。我的查询有一些根本性的问题,它可能扫描了所有这 500 万行。
  • 没有。您的查询(除了不必要的外连接)很好,我的查询也很好。一定是 DBMS 出错了。它要么在不应该使用索引时使用索引,要么反之亦然。例如,如果 90% 的帖子有用户 1 条目,则不应通过索引读取帖子表。用户 1 有多少个源 ID?对于这些 ID,表 posts 中有多少帖子?索引relations(user_id, source_id) 是唯一索引,对吗?
  • 对于user_id=1posts 中有 1,031 个 source_id 和 454,006 行。这是简单的索引。
【解决方案5】:

我会尝试对关系使用复合索引:

INDEX source_user (user_id,source_id)

并将查询更改为:

SELECT      p.id, p.content
FROM        posts AS p
INNER JOIN   relations AS r 
ON ( r.user_id = 1 AND r.source_id = p.source_id )
ORDER BY    p.date DESC
LIMIT       10

【讨论】:

  • 以前在这里发布过这样的建议——对性能没有影响。
  • 在这个查询中你可以完全删除relations;它不影响结果。但正如已经解释的那样,外连接不适合 Osvaldas 实际上想要的。
  • 确实INNER JOIN 在这里更好,即使 OP 说复合索引没有改变任何东西,我仍然调整了我的查询。
猜你喜欢
  • 2023-02-01
  • 1970-01-01
  • 1970-01-01
  • 2012-10-25
  • 2017-07-13
  • 2019-09-12
  • 2011-02-22
  • 2016-10-23
  • 2011-09-11
相关资源
最近更新 更多