我有一个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秒。
reports_at列已建立索引。
所以有两个问题,首先是为什么这个order_by语句需要这么长时间,其次是如何加快速度呢?
编辑:在回答以下问题时,这里是使用说明时的输出:
'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`)
)
注意:我省略了一些非相关字段,因此索引多于字段,但您可以放心地假设所有索引的字段都在表中
答案 0 :(得分:2)
EXPLAIN
的输出显示,由于ORDER BY
子句,MySQL决定使用incidentsReportedAt
索引。它按索引提供的顺序从表数据中读取每一行,并检查其上的WHERE
条件。这需要从表数据中读取大量信息,这些信息分散在整个表中。不是一个好的工作流程。
OP在列reported_at
和report_ip
上创建了一个索引(如原始答案所示,见下文),执行时间从313秒降至133秒。一个改进,但还不够。我认为这个仍然很长的执行时间的原因是访问每一行的表数据以验证is_infringement = 1
子句的WHERE
部分,但即使将它添加到索引也无济于事。
OP在评论中说:
在进一步研究并将索引更改为相反的方式(
remote_ip
,reported_at
)之后,查询现在超快(0.083秒)。
实际上,这个索引更好,因为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作业等)。