我有一个sql查询如下
select *
from incidents
where remote_ip = '192.168.1.1' and is_infringement = 1
order by reported_at desc
limit 1;
目前这个查询需要 313.24 秒才能运行。
如果我删除 order by
那么查询就是
select *
from incidents
where remote_ip = '192.168.1.1' and is_infringement = 1
那么运行只需要 0.117 秒。
reported_at 列已编入索引。
所以有 2 个问题,首先为什么这个 order_by 语句要花这么长时间,其次我怎样才能加快它的速度?
编辑:在回答下面的问题时,这里是使用 explain 时的输出:
'1', 'SIMPLE', 'incidents', 'index', 'uniqueReportIndex,idx_incidents_remote_ip', 'incidentsReportedAt', '4', NULL, '1044', '100.00', 'Using where'
建表语句:
CREATE TABLE `incidents` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`incident_ip_id` int(10) unsigned DEFAULT NULL,
`remote_id` bigint(20) DEFAULT NULL,
`remote_ip` char(32) NOT NULL,
`is_infringement` tinyint(1) NOT NULL DEFAULT '0',
`messageBody` text,
`reported_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT 'Formerly : created_datetime',
`created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
`updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
PRIMARY KEY (`id`),
UNIQUE KEY `uniqueReportIndex` (`remote_ip`,`host_id_1`,`licence_feature`,`app_end`),
UNIQUE KEY `uniqueRemoteIncidentId` (`remote_id`),
KEY `incident_ip_id` (`incident_ip_id`),
KEY `id` (`id`),
KEY `incidentsReportedAt` (`reported_at`),
KEY `idx_incidents_remote_ip` (`remote_ip`)
)
注意:我省略了一些不相关的字段,因此索引比字段多,但您可以安全地假设所有索引的字段都在表中
最佳答案
EXPLAIN
的输出显示,由于 ORDER BY
子句,MySQL 决定使用 incidentsReportedAt
索引。它按照索引提供的顺序从表数据中读取每一行,并检查其上的 WHERE
条件。这需要从表数据中读取大量信息,这些信息分散在整个表中。不是一个好的工作流程。
更新
OP 在 reported_at
和 report_ip
列上创建了一个索引(如原始答案中所建议的,见下文),执行时间从 313 秒减少到 133 秒。有进步,但还不够。我认为执行时间仍然很长的原因是访问每一行的表数据以验证 WHERE
子句的 is_infringement = 1
部分,但甚至将其添加到索引不会有太大帮助。
OP 在评论中说:
Ok after further research and changing the index to be the other way round (
remote_ip
,reported_at
) the query is now super fast (0.083 sec).
这个索引确实更好,因为 remote_ip = '192.168.1.1'
条件过滤掉了很多行。 使用现有的 uniqueReportIndex
索引可以达到相同的效果。 reported_at
上的原始索引可能欺骗了 MySQL,使其认为最好用它来按照 ORDER BY
要求的顺序检查行,而不是先过滤,最后排序。
我认为 MySQL 在 (remote_ip
, reported_at
) 上使用新索引进行过滤 (WHERE remote_ip = '192.168.1.1'
) 并且用于排序(ORDER BY reported_at DESC
)。 WHERE
条件提供了一个小的候选行列表,这些行很容易识别,也可以使用该索引进行排序。
原始答案如下。
它提供的建议不正确,但它帮助 OP 找到了正确的解决方案。
按顺序在 reported_at
和 report_ip
列上创建索引
然后看看 EXPLAIN
说了什么以及查询是如何执行的。它应该工作得更快。
您甚至可以在 reported_at
、report_ip
和 is_infringement
列上创建新索引(索引中列的顺序非常重要) .
三列索引帮助MySQL识别行而不需要读取表数据(因为WHERE
和ORDER BY
子句的所有列都在索引中).由于 SELECT *
,它只需要读取它返回的行的表数据。
创建新索引(在两列或三列上)后,删除旧索引 incidentsReportedAt
。不再需要它了;它使用磁盘和内存空间,需要时间来更新,但未被使用。将改用新索引(第一个位置有 reported_at
列)。
在 is_infringement = 1
条件下,两列上的索引需要更多的表数据读取。查询可能比使用三列索引运行得慢一些。另一方面,表更新以及磁盘和内存空间使用量有所增加。
在两列或三列上建立索引的决定取决于问题中发布的查询运行的频率及其服务对象(访问者、管理员、cron 作业等)。
关于mysql - Order By 导致我的查询运行非常慢,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45209484/