【问题标题】:MySQL ORDER BY causing extreme slow down with LEFT JOINMySQL ORDER BY 使用 LEFT JOIN 导致极度缓慢
【发布时间】:2018-04-02 12:37:39
【问题描述】:

我有一个查询如下:

SELECT DISTINCT O.MessageID, OD.Destination 
FROM OutboundMessages AS O LEFT JOIN 
     OutboundMessagesDetails AS OD 
     ON OD.MessageID=O.MessageID
WHERE O.UserID = 18097 AND
      O.Status IS NOT NULL AND
      O.Status <> 'Deleted' 
ORDER BY O.ScheduleDate DESC
LIMIT 0, 25

大约需要 50 秒才能完成。解释如下:

id  select_type     table   partitions  type    possible_keys           key             key_len     ref                         rows    filtered    Extra   
1   SIMPLE          O       NULL        index   PRIMARY,UserSchedule    UserSchedule    4           NULL                        15055   0.08        Using where; Using temporary
1   SIMPLE          OD      NULL        eq_ref  PRIMARY                 PRIMARY         8           NMV2_Messaging.O.MessageID  1       100.00      Using index; Distinct

请注意,ORDER BY 子句位于第一个表中的字段上 (OutboundMessages AS O)

如果我删除 ORDER BYLEFT JOIN 需要 0.00035 秒才能完成。

为什么会有这么慢的速度?大概是因为 MySQL 在执行ORDER BY 之前的每一行都是LEFT JOINing。如果这是正确的,有没有办法可以防止这种情况并让 MySQL 在过滤、限制和排序之后执行LEFT JOIN

【问题讨论】:

  • 请确保加入的表字段具有相同的数据类型。我的意思是 OutboundMessagesDetails.MessageID 和 OutboundMessages.MessageID 必须是相同的数据类型才能快速运行。
  • 这些表上有哪些索引(和键)?
  • @LokeshKumarGaurav - 数据类型相同。
  • @Paul Spiegel - 主键是 MessageID,索引在 (UserID, ScheduleDate) (除其他外,但这是这里唯一相关的索引)
  • 如果MessageID 在两个表中都是PK(一对一关系),那么您不需要DISTINCT,因为您的查询不可能重复。但是 - 即使引擎需要对 15K 行进行两次排序(一次用于 DISTINCT,一次用于 ORDER BY),也不应该花费 50 秒。

标签: mysql sql indexing left-join sql-order-by


【解决方案1】:

为了实际上只读取 25 行(参见 LIMIT 25),INDEX 需要通过 ORDER BY

要使INDEX 超过ORDER BY,索引需要以ORDER BY 中的列结尾(在您的情况下为ScheduleDate;还有其他条件,但都满足)。 而且你需要完全通过WHERE子句。

完全通过WHERE子句,所有AND'd子句必须是column = constant&lt;&gt; 不会。 IS NOT NULL 不会。范围(在您的情况下不存在)不会执行除非它与ORDER BY 相同。

所以,这是不可能的。

无论如何,DISTINCT(或GROUP BY)意味着它必须在计算完 25 行之前进行重复数据删除。

但是DISTINCT 真的需要吗?那么,对于给定的MessageID,是否可以有多个相同 Destination 副本?如果没有,DISTINCT 会为你做点什么吗?

为什么有LEFT?这意味着Destination 是可选的。

这是另一种表述;它可能会更好,也可能不会更好:

SELECT  O.MessageID, 
    (   SELECT  Destination
            FROM  OutboundMessagesDetails
            WHERE  MessiageID = O.MessageID 
    ) AS Destination
    FROM  OutboundMessages AS O
    WHERE  O.UserID = 18097
      AND  O.Status IS NOT NULL
      AND  O.Status <> 'Deleted'
    ORDER BY  O.ScheduleDate DESC
    LIMIT  0, 25

注意:内部SELECT可能需要DISTINCT

你需要

INDEX(UserID,    -- first
      ScheduleDate, -- second
      Status, MessageID)  -- (either order) to make it "covering"

哦,Status 的可能值是多少?如果只有一个其他选择,请将 both 子句替换为 AND O.Status = 'Valid'。现在你可以用它来一路通关了!

INDEX(UserID, Status, ScheduleDate, MessageID)

请注意,这与我之前的建议不同。

注意:NULL 不等于任何东西,甚至不等于 NULL

而且,是的,另一个表需要 INDEX(MessageID, Destination)(除非它有 PRIMARY KEY(MesssageID) 并且是 InnoDB)。

【讨论】:

  • 对于另一个表索引,如果它有 30 列并且我选择了其中的 29 列,而不仅仅是目标,该怎么办?我需要一个涵盖所有 29 个的索引吗? MessageID 是两个表中的主键,它们是 InnoDB,所以我猜它不适用,但我很好奇 :)
  • @BenHolness - 不。如果要获取的列多于几列,请不要打扰任何额外的列。没有得到“覆盖”的好处,但仍然得到其他好处。
【解决方案2】:

对于这个查询:

SELECT DISTINCT O.MessageID, OD.Destination 
FROM OutboundMessages O LEFT JOIN 
     OutboundMessagesDetails OD 
     ON OD.MessageID = O.MessageID
WHERE O.UserID = 18097 AND
      O.Status IS NOT NULL AND
      O.Status <> 'Deleted' 
ORDER BY O.ScheduleDate DESC
LIMIT 0, 25;

您希望在 OutboundMessages(UserID, Status, MessageId, Scheduledate)OutboundMessagesDetails(MessageID, Destination) 上建立索引。

SELECT DISTINCT 也会降低查询速度。如果不需要,则将其删除。

我想指出您的查询排序没有意义,因为您有SELECT DISTINCT,然后查询按不在SELECT 中的列排序。大多数数据库会拒绝这一点。 MySQL允许它。在这种特殊情况下,这是合理的,因为DISTINCT 位于(可能)同一个表中的主键上。

【讨论】:

  • 该查询没有意义,主要是因为我从原始查询中删除了很多内容以简化它,以便提出这个问题并错过了这种不一致之处。您可以将ScheduleDate 视为SELECT 中的列之一。
  • 出于好奇,您为什么建议在 OutboundMessagesDetails(MessageID, Destination) 上建立索引?它只是被选择,而不是过滤或排序。
  • @BenHolness 。 . .对于JOINSELECT 列。
  • 我很想知道它是如何产生性能差异的,我认为索引实际上只与属于JOIN 子句的字段相关,属于WHERE 条件的一部分,或者是ORDER BY 部分的一部分。我不明白它在这里有什么帮助,有问题的字段(目的地)只是SELECTed
  • @BenHolness - 我认为 Gordon 正在制作 Covering Indexes
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-03-02
  • 1970-01-01
  • 1970-01-01
  • 2019-08-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多