【问题标题】:MySQL query becomes extremely slow when using Order By使用 Order By 时 MySQL 查询变得非常慢
【发布时间】:2017-04-01 15:20:14
【问题描述】:

我有一个包含 1500 万行的 messages 表。

以下查询在不到 1 秒的时间内返回大约 500 万条(但限于 15 行)记录:

SELECT messages.* FROM messages 
INNER JOIN gateways ON
messages.gateway_id=gateways.id
INNER JOIN orders ON
gateways.order_id=orders.id
WHERE orders.user_id=6500 AND messages.deleted=0
AND messages.type='Out' LIMIT 15;

但是当我在它的末尾添加一个Order ByidDESC 时,它会变得非常慢大约 40 秒:

SELECT messages.* FROM messages 
INNER JOIN gateways ON
messages.gateway_id=gateways.id
INNER JOIN orders ON
gateways.order_id=orders.id
WHERE orders.user_id=6500 AND messages.deleted=0
AND messages.type='Out' ORDER BY messages.id DESC LIMIT 15;

任何帮助将不胜感激。

【问题讨论】:

  • 您是否已经有一个按您需要的列按 DESC 顺序排列的索引?
  • 是的,我在主键列(id)上order by
  • 你得到了多少行(每个用户有多少行)?你能添加解释计划吗?为order by 使用id 上的索引很可能不是一个好的选择,但无论如何MySQL 都可能会使用它。您可能需要一个子查询。
  • 请提供SHOW CREATE TABLE
  • “返回 500 万条记录”,LIMIT 15。出了点问题。请修复。

标签: mysql performance query-optimization database-performance


【解决方案1】:

SELECT lots_of_stuff ORDER BY something LIMIT small_integer 模式因引起性能问题而臭名昭著。省略ORDER BY something 可以消除性能问题。为什么?因为ORDER BY 的模式导致 MySQL 服务器对大量相当大的行(在您的情况下为 500 万行)进行排序,只丢弃除少数之外的所有行。这会在您的服务器中使用大量 RAM、CPU 和 IO,只是为了丢弃大部分工作。

您最好的选择是在这里使用延迟连接类型的模式,除了message.id 值之外什么都不排序。使用这个子查询来做到这一点。

                   SELECT messages.id 
                     FROM messages 
               INNER JOIN gateways ON messages.gateway_id=gateways.id
               INNER JOIN orders   ON gateways.order_id=orders.id
                    WHERE orders.user_id=6500
                      AND messages.deleted=0
                      AND messages.type='Out'
                 ORDER BY messages.id DESC
                   LIMIT 15

这将为您提供一个包含 15 个message.id 值的漂亮小集合。

您的下一步是优化此子查询。我建议您在messages 表上尝试一个复合覆盖索引,其中包含(deleted, type, id, gateway_id) 列。这应该有助于加速它。

您可能还需要其他表的索引。您应该考虑使用 MySQL 中的EXPLAIN 函数来分析您的性能。

最后,使用messages.id 值的小集合来获取您需要的messages 行,就像这样。 (这是延迟连接;您将推迟获取整行,直到您知道需要哪些行。这样您就不必ORDER 整个混乱。)

编辑gateways (order_id, id) 上添加复合索引以避免对该表进行全表扫描。它不是很大,但这可能会有所帮助。

SELECT a.*
  FROM messages a
  JOIN (
                   SELECT messages.id 
                     FROM messages 
               INNER JOIN gateways ON messages.gateway_id=gateways.id
               INNER JOIN orders   ON gateways.order_id=orders.id
                    WHERE orders.user_id=6500
                      AND messages.deleted=0
                      AND messages.type='Out'
                 ORDER BY messages.id DESC
                   LIMIT 15
       ) b ON a.id = b.id
 ORDER BY a.id DESC

【讨论】:

  • 为了其他人试图优化事物,请考虑发表评论,说明这对您的效果如何。
  • 我已经在(deleted, type, gateway_id) 上有一个复合索引。我应该在其中添加id 列吗?
  • 我认为你应该,是的。但是您需要使用EXPLAIN 才能确定。
  • 关于 ORDER BY + LIMIT 的注释:Postgresql 有一个非常有用的模式,称为 top-n heapsort 来处理这个问题。如果 LIMIT 很小,它基本上和不排序一样快。我希望 MySQL 也能做到!
【解决方案2】:

我认为

  • 每个订单属于一个用户
  • 每个网关属于一个订单

因此,这个:

INNER JOIN gateways ON messages.gateway_id=gateways.id
INNER JOIN orders   ON gateways.order_id=orders.id
WHERE orders.user_id=6500 AND messages.deleted=0

可以改写成英文:

"获取属于该用户的订单的网关"。

现在,要获取与该用户相关的最新消息,问题是我们可能有许多不同的 gateway_id(根据您的说明大约有 143 个),因此我们不能使用索引来跳过排序。

好吧,正如 O. Jones 所展示的那样,我们可以,但有一个问题。这是查询的简化版本:

SELECT ... FROM messages
WHERE gateway_id IN (1,2) ORDER BY id DESC LIMIT 10

如果我们在 (id,gateway_id) 上有一个索引,那么 MySQL 很可能会决定按降序扫描它。如果它很快找到 10 条具有“gateway_id IN (1,2)”的消息,那么它会很快。但是,如果这些 gateway_id 有非常旧的消息,或者根本没有消息,它可能需要扫描整个索引。

如果 PK 关系如我所述,我将在消息表中具体化一个 user_id 列,然后允许在 (user_id,message_id) 上建立索引,从而将查询时间安排在毫秒以内。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-05-15
    • 1970-01-01
    • 2019-09-02
    • 1970-01-01
    相关资源
    最近更新 更多