即使我有索引,MySQL ORDER BY 也需要很长时间

Posted

技术标签:

【中文标题】即使我有索引,MySQL ORDER BY 也需要很长时间【英文标题】:MySQL ORDER BY takes a very long time even if I have indexes 【发布时间】:2017-02-18 15:12:11 【问题描述】:

我有以下 mysql 查询,在 linux VM 上大约需要 40 秒:

SELECT
* FROM `clients_event_log`
WHERE
`ex_long` = 1475461 AND 
`type` in (2, 1) AND NOT 
(
    (category=1 AND error=-2147212542) OR
    (category=7 AND error=67)
)
    ORDER BY `ev_time` DESC LIMIT 100

该表大约有 700 万行。大小为 800 MB,它对 WHERE 和 ORDER BY 子句中使用的所有字段都有索引。

现在,如果我更改查询以在外部 SELECT 中完成排序,一切都会更快(大约 100 毫秒):

SELECT res.* FROM
(
  SELECT * FROM `clients_event_log`
  WHERE 
  `ex_long` = 1475461 AND 
  `type` in (2, 1) AND NOT 
  (
    (category=1 AND error=-2147212542) OR
    (category=7 AND error=67)
  )
) AS res
    ORDER BY res.ev_time DESC LIMIT 0, 100

您知道为什么第一个查询需要这么长时间吗?谢谢。


稍后更新:

第一个查询解释:

id  select_type table   type    possible_keys   key key_len ref rows    Extra
1   SIMPLE  clients_event_log   index   category,ex_long,type,error,categ_error ev_time 4   NULL    5636    Using where

第二个查询解释:

id  select_type table   type    possible_keys   key key_len ref rows    Extra
1   PRIMARY <derived2>  system  NULL    NULL    NULL    NULL    1   
2   DERIVED clients_event_log   ref category,ex_long,type,error,categ_error ex_long 5       131264  Using where

表定义:

CREATE TABLE `clients_event_log` (
  `ev_id` int(11) NOT NULL,
  `type` int(6) NOT NULL,
  `ev_time` int(11) NOT NULL,
  `category` smallint(6) NOT NULL,
  `error` int(11) NOT NULL,
  `ev_text` varchar(1024) DEFAULT NULL,
  `userid` varchar(20) DEFAULT NULL,
  `ex_long` int(11) DEFAULT NULL,
  `client_ex_long` int(11) DEFAULT NULL,
  `ex_text` varchar(1024) DEFAULT NULL,
  PRIMARY KEY (`ev_id`),
  KEY `category` (`category`),
  KEY `ex_long` (`ex_long`),
  KEY `type` (`type`),
  KEY `ev_time` (`ev_time`),
  KEY `error` (`error`),
  KEY `categ_error` (`category`,`error`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

【问题讨论】:

当然不应该这样。查询在语义上是相等的,并且应该导致相同的执行计划。由于显然不是这样,我认为这是 DBMS 中的一个缺陷。 您可能在所有字段上都有单个索引,例如ex_long 上的一个索引,type 上的一个索引,ev_time 上的一个索引。 MySQL 每个查询只能使用一个索引,并且可能在您的第一个查询中使用了错误的索引(例如ev_time 上的索引)。查看explain 输出以检查(并将其添加到您的问题中)。因此,要解决您的问题,您需要一个复合索引,例如ex_long, ev_time(取决于您的数据,也可能是其他列)或强制使用正确的单列索引(可能在您的第二个查询中使用的索引)。 第一个执行计划很愚蠢(通过索引访问 每个 记录只是为了按最终排序的顺序读取它们。)正如我所说: MySQL 优化器中的缺陷。 @ThorstenKettner 你是对的,它不应该有所作为,但在 MySQL 中,它确实如此。这是旧 mysql 版本中创建派生表的方式,因此 MyQSL 不会在内部查询中使用现在的外部 order by-index。 5.7会正确执行。 @Julian 您的explain 显示,正如预期的那样,您在查询中使用了不同的索引。您可以添加复合索引(根据需要添加任意数量的列,但我猜 2 应该没问题),或者(或者如果由于某种原因它仍然无法工作)您可以使用 from `clients_event_log` force index (ex_long) 强制正确的索引您的第一个查询。 【参考方案1】:

我最终使用了第二个查询(内部 SELECT),因为 MySQL 优化器决定始终使用 ev_time 索引,即使我尝试了包含 WHERE 和 ORDER BY 子句中的列的复合索引的多个版本。

使用force index (ex_long) 也有效。

MySQL 版本是 5.5.38

谢谢。

【讨论】:

请记住,当您更新到 5.7 时,此代码可能不再起作用。您的limit 允许mysql 选择ev_time 上的索引,因此一旦不再使用该语法创建派生表,它可能会再次这样做,因此force index(在复合索引或单个索引上) ) 将是更安全的选择。或者在您的桌子上尝试optimize,您的统计信息可能会关闭。【参考方案2】:

添加这些

INDEX(ev_long, ev_time),
INDEX(ev_long, type)

并使用第一种查询格式,让优化器根据统计信息决定哪种格式更好。

【讨论】:

【参考方案3】:
SELECT provider.* FROM user,person,provider,meta_data_specialisation
where user.person_id = person.id and user.id = provider.user_id and
provider.specialization_id = meta_data_specialisation.id and
( user.mobile like '%+9143%')
 order by provider.created_date_and_time desc limit 0 , 10;

有人可以优化这个查询,它花费了太多时间,如果我删除订单,它会在几毫秒内执行。

【讨论】:

以上是关于即使我有索引,MySQL ORDER BY 也需要很长时间的主要内容,如果未能解决你的问题,请参考以下文章

MySQL实验 内连接优化order by+limit 以及添加索引再次改进

MySQL 在 VARCHAR 上引用索引前缀时如何使用 ORDER BY?

如果 ASC 和 DESC 混合使用,为啥 MySQL 不能为 ORDER BY 使用索引?

MYSQL在简单查询中使用ORDER BY索引列时使用文件排序

MySQL 原理分析之 Trace 分析 order by 的索引原理

MySQL优化:order by和limit