优化连接查询中的 order by 子句

Posted

技术标签:

【中文标题】优化连接查询中的 order by 子句【英文标题】:Optimizing order by clause in join query 【发布时间】:2013-10-05 17:18:23 【问题描述】:

我需要帮助来优化此查询。

  SELECT messages.*
   FROM messages
   INNER JOIN subscription ON subscription.entity_id = messages.entity_id
   WHERE subscription.user_id = 1
   ORDER BY messages.timestamp DESC 
   LIMIT 50

没有限制,此查询返回 200K 行,运行大约需要 1.3 - 2 秒。问题似乎出在 order by 子句中。没有它,查询需要 0.0005 秒。

Indexes:
    ( subscription.user_id, subscription.entity_id )
    ( subscription.entity_id )
    ( messages.timestamp )
    ( messages.entity_id, messages.timestamp )

我可以通过将查询更改为以下内容来提高性能:

SELECT messages.* FROM messages
INNER JOIN subscription ON subscription.entity_id = messages.entity_id 
INNER JOIN ( 
   SELECT message_id FROM messages ORDER BY timestamp DESC
) as temp on temp.messsage_id = messages.message_id
WHERE subscription.user_id = 1 LIMIT 50

这将在 0.12 秒内运行。一个非常好的改进,但我想知道它是否可以更好。它似乎 如果我能以某种方式过滤第二个内部连接,那么事情会更快。

谢谢。

架构:

   messages 
      message_id, entity_id, message, timestamp

   subscription
      user_id, entity_id

更新

Raymond Nijland 的答案解决了我最初的问题,但又出现了一个问题

 SELECT messages.*
   FROM messages
   STRAIGHT_JOIN subscription ON subscription.entity_id = messages.entity_id
   WHERE subscription.user_id = 1
   ORDER BY messages.timestamp DESC 
   LIMIT 50

直接连接在两种情况下效率低下:

    订阅表中没有user_id条目

    消息表中相关条目很少

关于如何解决此问题的任何建议?如果不是从查询的角度来看,是应用程序吗?

更新

解释信息

限制 50

| id | select_type | table             | type   | possible_keys                           | key           | key_len | ref                                    | rows | Extra       |
|  1 | SIMPLE      | messages          | index  | idx_timestamp                           | idx_timestamp | 4       | NULL                                   |   50 |             |
|  1 | SIMPLE      | subscription      | eq_ref | PRIMARY,entity_id,user_id               | PRIMARY       | 16      | const, messages.entity_id              |    1 | Using index |

没有限制

| id | select_type | table             | type   | possible_keys                           | key           | key_len | ref                                    |   rows   | Extra         |
|  1 | SIMPLE      | messages          | ALL    | entity_id_2,entity_id                   | NULL          | NULL    | NUL                                    |   255069 | Using filesort|
|  1 | SIMPLE      | subscription      | eq_ref | PRIMARY,entity_id,user_id               | PRIMARY       | 16      | const, messages.entity_id              |        1 | Using index   |

创建表语句:

约 5000 行

subscription | CREATE TABLE `subscription` (
  `user_id`   bigint(20) unsigned NOT NULL,
  `entity_id` bigint(20) unsigned NOT NULL,
  PRIMARY KEY (`user_id`,`entity_id`),
  KEY `entity_id` (`entity_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

约 255,000 行

messages | CREATE TABLE `messages` (
  `message_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `entity_id` bigint(20) unsigned NOT NULL,
  `message` varchar(255) NOT NULL DEFAULT '',
  `timestamp` int(10) unsigned NOT NULL,
  PRIMARY KEY (`message_id`),
  KEY `entity_id` (`entity_id`,`timestamp`),
  KEY `idx_timestamp` (`timestamp`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 

【问题讨论】:

你能发布 show create table 语句吗? 单个用户20万行?你确定吗? @DanBracuk 是的,我确定 你能在没有 user_id 的情况下运行 EXPLAIN 并且在消息的情况下很少相关条目并在此处发布结果吗? 如果您希望我们帮助优化查询,您需要向我们展示表和索引定义,以及每个表的行数。也许您的表格定义不佳。也许索引没有正确创建。也许您认为您在该列上没有索引。没有看到表和索引定义,我们无法判断。我们还需要行计数,因为这会极大地影响查询优化。如果您知道如何处理EXPLAIN 或获取执行计划,请将结果也放入问题中。 【参考方案1】:

删除索引messages.entity_id 这个是冗余的并尝试直接加入我认为mysql 优化器以错误的顺序访问您的表。 MySQL 需要首先访问表消息,以便它可以使用消息上的索引(实体 ID、时间戳)并消除对“使用临时;使用文件排序”的需要(如果 MySQL 需要创建基于 MyISAM 磁盘的表并且需要使用磁盘 I/O 读取和 I/O 写入排序(快速排序算法)。

 SELECT STRAIGHT_JOIN messages.*
   FROM messages
   INNER JOIN subscription ON subscription.entity_id = messages.entity_id
   WHERE subscription.user_id = 1
   ORDER BY messages.timestamp DESC 
   LIMIT 50

 SELECT messages.*
   FROM messages
   STRAIGHT_JOIN subscription ON subscription.entity_id = messages.entity_id
   WHERE subscription.user_id = 1
   ORDER BY messages.timestamp DESC 
   LIMIT 50

我也遇到过这个问题,我像 http://sqlfiddle.com/#!2/b34870/1 那样修复了它,但后来使用国家/城市表

编辑因为 Jason M 对 STRAIGHT_JOIN 的反应

直接连接在两种情况下效率低下:

订阅表中没有 user_id 条目

事实上,带有 INNER JOIN 的 MySQL 优化器会触发“在读取 const 表后注意到不可能的 WHERE”并且永远不会执行查询。 但是 STRAIGHT_JOIN 不会触发“在读取 const 表后注意到不可能的 WHERE”,因此需要进行(可能是完整的)索引扫描以找到可能会减慢查询执行速度的 user_id 值。 简单的解决方法是:将现有的 user_id 与 STRAIGHT_JOIN 一起使用

消息表中的相关条目很少

这里可能存在同样的问题 MySQL 认为它应该进行(可能是完整的)索引扫描以查找结果。但我需要查看 EXPLAIN 语句才能确定

您可能还想先尝试此查询

SELECT 
 *
FROM (

 SELECT
  entity_id

 FROM
  subscriptions

 WHERE
  subscription.user_id = 1 
)
 subscriptions

INNER JOIN 
 messages

ON
 subscriptions.entity_id = messages.entity_id

ORDER BY
 messages.timestamp DESC

LIMIT 50  

【讨论】:

文件排序不一定很慢。而且是用词不当,它确实不是的意思是通过磁盘文件执行! 我知道...我的意思是“使用临时;使用文件排序”这可能会导致使用快速排序算法和大量磁盘 IO 对基于 MyISAM 磁盘的临时表进行排序 谢谢雷蒙德,这绝对解决了我的问题。查询现在在 0.000x 秒内运行 @ypercube 我不完全确定 filesort 不会触发基于磁盘的 IO,因为在这条线下 IO_CACHE tempfile, buffpek_pointers, *outfile;在 sql/filesort.cc 中,这意味着文件排序可能会触发一些基于磁盘的 IO 写入和读取,但我还没有完全从源代码中分析这部分 @Jason M 提示下次运行 EXPLAIN 时,您可以看到 MySQL 将尝试访问您的表的顺序。如果您在表一上使用 GROUP BY/ORDER BY。 MySQL 需要先读取表一。如果这是错误的顺序,则使用 STRAIGHT_JOIN 强制它。如果表一的记录较少,则MYSQL优化器也不是基于成本的,如果表二的记录较少,则首先访问此表。..

以上是关于优化连接查询中的 order by 子句的主要内容,如果未能解决你的问题,请参考以下文章

MySQL调优--05---多表查询优化子查询优化 ORDER BY优化GROUP BY优化分页查询优化

在连接中使用 Where 子句,以及 Group by 和 Order By

mysql GROUP BY、DISTINCT、ORDER BY语句优化

多次连接后,Order by 子句的行为不正确

查询优化--ORDER BY查询优化

简述SELECT语句中的FROM、WHERE以及ORDER BY子句的作用。SQL Server