使用LEFT JOIN导致MySQL ORDER BY极端减速

时间:2017-10-21 14:33:49

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

我的查询如下:

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在LEFT JOINing之前的每一行都是ORDER BY。如果这是正确的,有没有办法可以阻止这种情况并让MySQL在过滤,限制和排序后执行LEFT JOIN

2 个答案:

答案 0 :(得分:1)

为了实际只读取25行(请参阅LIMIT 25),INDEX需要超过ORDER BY

要让INDEX超过ORDER BY,索引需要以ORDER BYScheduleDate中的列结束,在您的情况下;还有其他条件,但他们相遇)。 您需要完全了解WHERE子句。

要通过WHERE子句完全 所有AND'd条款必须为column = constant<>不会做。 IS NOT NULL不会做。范围(在您的情况下不存在)不会,除非ORDER BY相同。

所以,这是不可能的。

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

但真的需要DISTINCT吗?那么,对于给定的Destination,是否可以存在相同 MessageID的多个副本?如果没有,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有什么可能的值?如果只有另一个选择,则将两个子句替换为AND O.Status = 'Valid'。现在你可以用它来一直通过!

INDEX(UserID, Status, ScheduleDate, MessageID)

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

注意:NULL不等于任何内容,甚至不等于NULL

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

答案 1 :(得分:0)

对于此查询:

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(可能)是同一个表中的主键。