即使我有索引,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索引列时使用文件排序