我需要一些帮助来解决性能问题。存在约800万条记录后,包含单个表的METAR(航空天气报告)数量不断增加的数据库正在放缓。尽管正在使用索引。可以通过重建索引来恢复性能,但这确实很慢,并且会使数据库脱机,因此我只能删除表并重新创建表(丢失最后几周的数据)。
无论是运行查询来尝试检索实际元数据还是执行简单的select count(*)
,行为都是相同的。
表创建语法如下:
CREATE TABLE `metars` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`tstamp` timestamp NULL DEFAULT NULL,
`metar` varchar(255) DEFAULT NULL,
`icao` char(7) DEFAULT NULL,
`qnh` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `timestamp` (`tstamp`),
KEY `icao` (`icao`),
KEY `qnh` (`qnh`),
KEY `metar` (`metar`)
) ENGINE=InnoDB AUTO_INCREMENT=812803050 DEFAULT CHARSET=latin1;
[最多约800万条记录,select count(*)
在约500毫秒内返回。然后逐渐增加,目前又达到1400万条记录,计数需要3到30秒。我很惊讶地看到,在解释计数查询时,它使用时间戳作为索引,而不是主键。使用主键只需几毫秒即可返回记录数:
mysql> explain select count(*) from metars;
+----+-------------+--------+-------+---------------+-----------+---------+------+----------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+-------+---------------+-----------+---------+------+----------+-------------+
| 1 | SIMPLE | metars | index | NULL | timestamp | 5 | NULL | 14693048 | Using index |
+----+-------------+--------+-------+---------------+-----------+---------+------+----------+-------------+
1 row in set (0.00 sec)
强制它使用主索引甚至更慢:
mysql> select count(*) from metars use index(PRIMARY);
+----------+
| count(*) |
+----------+
| 14572329 |
+----------+
1 row in set (37.87 sec)
奇怪的是,典型的用例查询是获取距特定时间点最近的机场的天气,尽管比简单的计数更为复杂,但该机场的性能仍然很好:
mysql> SELECT qnh, metar from metars WHERE icao like 'KLAX' ORDER BY ABS(TIMEDIFF(tstamp, STR_TO_DATE('2019-10-10 00:00:00', '%Y-%m-%d %H:%i:%s'))) LIMIT 0,1;
+------+-----------------------------------------------------------------------------------------+
| qnh | metar |
+------+-----------------------------------------------------------------------------------------+
| 2980 | KLAX 092353Z 25012KT 10SM FEW015 20/14 A2980 RMK AO2 SLP091 T02000139 10228 20200 56007 |
+------+-----------------------------------------------------------------------------------------+
1 row in set (0.01 sec)
我在这里做错了什么?
InnoDB通过遍历某些索引来执行普通的COUNT(*)
。它希望使用最小的索引,因为这将需要触摸块的租赁数量。
PRIMARY KEY
与数据聚集在一起,因此索引实际上是最大的。
您使用的是哪个版本? TIMESTAMP
有所改变。也许这解释了为什么使用tstamp
而不是qnh
。
如果要通过使用DELETE
清除旧数据,请参见http://mysql.rjweb.org/doc.php/partitionmaint以获取更快的方法。
我假设数据是静态的;那是从来没有UPDATEd
吗?考虑建立和维护一个汇总表,也许按日期索引。这一天可能有各种各样的计数。然后,从该表中获取数据将比命中原始数据快得多。更多:http://mysql.rjweb.org/doc.php/summarytables