我正在寻找有关MySQL表上的索引如何工作的一些见解,因为我遇到了一些我不理解的问题。
让我们从我正在使用的表开始:
mysql> SHOW CREATE TABLE channeldata\G
*************************** 1. row ***************************
Table: channeldata
Create Table: CREATE TABLE `channeldata` (
`channel_id` smallint(3) unsigned NOT NULL,
`station_id` smallint(5) unsigned NOT NULL,
`time` datetime NOT NULL,
`reading` double NOT NULL DEFAULT '0',
`average` double NOT NULL DEFAULT '0',
`location_lat` double NOT NULL DEFAULT '0',
`location_lon` double NOT NULL DEFAULT '0',
`location_alt` double(8,3) DEFAULT '0.000',
`quality` smallint(3) unsigned DEFAULT '0',
PRIMARY KEY (`channel_id`,`station_id`,`time`),
KEY `composite3` (`station_id`,`channel_id`,`quality`) USING BTREE,
KEY `composite` (`channel_id`,`station_id`,`time`,`quality`) USING BTREE
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
/*!50100 PARTITION BY RANGE (YEAR(time))
(PARTITION p0 VALUES LESS THAN (2001) ENGINE = MyISAM,
PARTITION p1 VALUES LESS THAN (2002) ENGINE = MyISAM,
PARTITION p2 VALUES LESS THAN (2003) ENGINE = MyISAM,
PARTITION p3 VALUES LESS THAN (2004) ENGINE = MyISAM,
PARTITION p4 VALUES LESS THAN (2005) ENGINE = MyISAM,
PARTITION p5 VALUES LESS THAN (2006) ENGINE = MyISAM,
PARTITION p6 VALUES LESS THAN (2007) ENGINE = MyISAM,
PARTITION p7 VALUES LESS THAN (2008) ENGINE = MyISAM,
PARTITION p8 VALUES LESS THAN (2009) ENGINE = MyISAM,
PARTITION p9 VALUES LESS THAN (2010) ENGINE = MyISAM,
PARTITION p10 VALUES LESS THAN (2011) ENGINE = MyISAM,
PARTITION p11 VALUES LESS THAN (2012) ENGINE = MyISAM,
PARTITION p12 VALUES LESS THAN (2013) ENGINE = MyISAM,
PARTITION p13 VALUES LESS THAN (2014) ENGINE = MyISAM,
PARTITION p14 VALUES LESS THAN (2015) ENGINE = MyISAM,
PARTITION p15 VALUES LESS THAN (2016) ENGINE = MyISAM,
PARTITION p16 VALUES LESS THAN (2017) ENGINE = MyISAM,
PARTITION p17 VALUES LESS THAN (2018) ENGINE = MyISAM) */
1 row in set (0.00 sec)
我正在运行查询以选择2017年8月/ 9月/ 10月的数据。'读数'在一天中均匀分布,并且总是在10分钟的边界(即10:10:00,10:20:00,10:30:00等)。'读数的数量'从2017年5月起,每天相当于15.000。总的来说,P17分区的读数刚刚超过300万。
我希望得到一些帮助的查询如下:
SELECT
ROUND(`a`.`average`,2) `average`,
UNIX_TIMESTAMP(`a`.`time`) * 1000 time,
`a`.`station_id`
FROM
`argus`.`channeldata` PARTITION (p17) `a`
WHERE
((`a`.`station_id` = '3002' AND a.channel_id = '1') OR (`a`.`station_id` = '3004' AND a.channel_id = '1') OR [...] OR (`a`.`station_id` = '5052' AND a.channel_id = '1')) AND `a`.`time` BETWEEN "2017-08-17 00:00:00" AND "2017-10-13 23:59:59" AND `a`.`quality` IN('1') ORDER BY `a`.`time` ASC;
此查询格式化为明确显示WHERE
条件。
SELECT
ROUND(`a`.`average`,2) `average`,
UNIX_TIMESTAMP(`a`.`time`) * 1000 time,
`a`.`station_id`
FROM
`argus`.`channeldata` PARTITION (p17) `a`
WHERE
( (`a`.`station_id` = '3002' AND a.channel_id = '1')
OR (`a`.`station_id` = '3004' AND a.channel_id = '1')
OR [...]
OR (`a`.`station_id` = '5052' AND a.channel_id = '1'))
AND `a`.`time` BETWEEN "2017-08-17 00:00:00" AND "2017-10-13 23:59:59"
AND `a`.`quality` IN('1')
ORDER BY `a`.`time` ASC;
为了获得一些指标,我开始从4周,5周等间隔中选择读数。这些查询完成的执行时间大约为4-5秒,稍微增加,我添加到间隔的天数就越多。但是,突然间执行时间会有所增加。只需添加一天即可获得' BETWEEN'间隔几乎使执行时间翻了两番,接近20秒。
我跑了之前&在解释中查询后,结果是我不明白的。
间隔为BETWEEN "2017-08-18 00:00:00" AND "2017-10-13 23:59:59"
EXPLAIN如下所示:
+----+-------------+-------+-------+------------------------------+---------+---------+------+--------+-----------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+------------------------------+---------+---------+------+--------+-----------------------------+
| 1 | SIMPLE | a | range | PRIMARY,composite3,composite | PRIMARY | 12 | NULL | 542026 | Using where; Using filesort |
+----+-------------+-------+-------+------------------------------+---------+---------+------+--------+-----------------------------+
1 row in set (0.00 sec)
将此值增加一天至BETWEEN "2017-08-17 00:00:00" AND "2017-10-13 23:59:59"
,如下所示:
+----+-------------+-------+------+------------------------------+------+---------+------+---------+-----------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+------------------------------+------+---------+------+---------+-----------------------------+
| 1 | SIMPLE | a | ALL | PRIMARY,composite3,composite | NULL | NULL | NULL | 3056618 | Using where; Using filesort |
+----+-------------+-------+------+------------------------------+------+---------+------+---------+-----------------------------+
1 row in set (0.00 sec)
那里发生了什么?为什么它突然无法使用主键/索引,而不是搜索行的子集,它必须搜索整个300万的主分区。在旁注中,间隔的确切位置并不重要。我也可以通过提前一个月移动间隔来重现这个问题。
如果它有帮助,那么在“跳跃”之前返回的列就是'在执行时间是525644,我加1天的数字是535004。
答案 0 :(得分:2)
您的过滤条件是:
quality
time
station_id
和channel_id
上的一对配对匹配。处理标准2和3的索引是您需要的。将等于匹配列放在索引中,然后放入范围扫描列,然后将索引与查询获取covering index所需的其他列进行四舍五入。
该索引为(quality, time, station_id, channel_id, average)
为什么会这样?查询计划程序可以立即跳转到索引的第一个符合条件的行,因为它知道所需的quality
和开始time
。然后,它可以按顺序扫描索引,执行成对匹配并检索average
列。 MySQL可以从索引中满足整个查询,从而节省大量跳回到表中以获取信息,从而加快了速度。
您已在(channel_id,station_id,time,quality)
上拥有索引。您可能希望在创建新索引时删除该索引,因为它看起来像是用于类似目的。
为什么查询规划器有时会使用索引,有时不使用索引?这取决于很多事情,主要是查询规划者估计它是否必须使用索引做更少的工作或只是扫描表。索引和列包含基数的估计值 - 数据项中不同值的数量。那些基数是估计值,有时它们非常不准确。您有分区:这可能会导致查询计划程序以某种方式限制其选择。查询规划器无法弄清楚要做什么的后备是你得到的:全表扫描。
您的问题中提到的索引已经需要相当多的费力索引扫描来满足查询;我想当您更改日期戳范围时,查询计划程序会切换到全表扫描策略。对于运行基于DBMS的软件的人来说,这是一个麻烦:随着应用程序的增长,有时查询计划员会突然转向一个新的低效计划。您需要始终掌握突然的性能变化并添加索引。
专业提示:与构建更好的索引相比,询问为什么关于查询规划器选择通常是徒劳无功的企业。 (除非您的开发工作正在处理查询规划器。)
我建议使用五列索引。您的查询使用四列进行过滤,然后使用最后一列显示结果。在索引中包含所有五列意味着MySQL不必返回主表中索引找到的各个行。它可以单独满足索引的查询,这意味着它可以从大容量存储中顺序读取索引。在传统的旋转硬盘驱动器上,这意味着读取头不必在索引到表之间来回传递tick-tock-tick-to来满足查询。它快得多。它被称为covering index。
专业提示:将BETWEEN
用于日期戳范围是一个错误。而不是使用
WHERE time BETWEEN '2017-08-17 00:00:00' AND '2017-10-13 23:59:59'
使用它。它在范围的最后更精确。它仍然可以扫描范围。
WHERE time >= '2017-08-17'
AND time < '2017-10-13' + INTERVAL 1 DAY
答案 1 :(得分:1)
优化器有两种方法可以在一个范围内执行索引查询:
选项1,使用索引:
WHERE
条件不匹配的行。选项2,忽略索引并扫描数据。
WHERE
条件的行。做一种方法和做另一种方法之间的界限取决于很多统计数据等。它通常在表的10%到30%之间。你注意到边界有一个很大的跳跃;这是因为统计数据并不完美。这种跳跃可能会变得更好或更糟。
旁注。一旦你有了Ollie更好的索引,分区就不会给你带来性能。实际上它可能会减慢查询速度。
lat / lng / alt的 DOUBLE
(8字节)是过度杀伤。请参阅my representation choices。
DOUBLE(8,3)
(仍然是8个字节)更糟糕;永远不要在(m,n)
或FLOAT
上使用DOUBLE
。
平均值的平均值在数学上是不正确的。考虑保留总和和计数,然后计算SUM(sum)/SUM(count)
以获得正确的AVG
。
想要获得每周结果10倍的速度吗?每天在汇总表中构建和维护计数和总和。这会使数据缩小1/144。然后通过汇总总和表上的discussion来汇总报告。