table 上的索引一直未被使用
Index on a table not being used all the time
我正在寻找有关 MySQL table 上的索引如何工作的一些见解,因为我遇到了一些我不明白的问题。
让我们从我正在使用的table开始:
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 年 August/Sept/Oct 中的 select 数据。'readings' 在一天中均匀分布,并且始终在 10 分钟的边界上(即 10:10:00、10:20:00、10:30:00 等)从 2017 年 5 月起,每天 'readings' 的数量相当稳定,为 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;
为了获得一些指标,我开始 select 从 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 万行以查找该分区。在旁注中,间隔的确切位置并不重要。我也可以通过将间隔提前一个月来重现此问题。
如果有帮助,执行时间在'jump'之前返回的列是525644,加上1天后的数字是535004。
您的过滤条件是:
- 明确的分区选择
- 在
quality
上的相等匹配
time
的范围扫描
- 在
station_id
和 channel_id
上的一组成对匹配。
处理标准 2 和 3 的索引就是您所需要的。将等式匹配列首先放入索引中,然后是范围扫描列,然后用查询所需的其他列对索引进行四舍五入以获得 covering index。
那个索引是(quality, time, station_id, channel_id, average)
为什么有效?查询规划器可以立即跳转到索引的第一个符合条件的行,因为它知道需要 quality
和起始 time
。然后它可以按顺序扫描索引,进行成对匹配并检索 average
列。 MySQL可以满足从索引开始的整个查询,省去了很多跳回table获取信息的过程,所以速度也很快。
您已经在 (channel_id,station_id,time,quality)
上建立了索引。您可能希望在创建新索引时删除该索引,因为看起来它的用途相似。
为什么查询规划器有时使用索引有时不用?这取决于很多事情,主要是查询规划器估计它是否需要使用索引做更少的工作或只扫描 table。索引和列包含基数估计——数据项中不同值的数量。这些基数是估计值,有时非常不准确。您有分区:这可能会导致查询计划程序以某种方式限制其选择。当查询规划器无法弄清楚要做什么时的回退就是你得到的:完整的 table 扫描。
您问题中提到的索引已经需要相当费力的索引扫描才能满足查询;我想当您更改日期戳范围时,查询计划器会切换到完整的 table 扫描策略。这对于操作基于 DBMS 的软件的人来说是一个麻烦:随着应用程序的增长,有时查询计划器会突然转向一个新的且效率较低的计划。您需要掌握突然的性能变化并添加索引。
专业提示:与构建更好的索引相比,询问为什么查询计划程序的选择通常是徒劳的。 (除非你的开发工作是在查询规划器上工作。)
我建议使用五列索引。您的查询使用四列进行过滤,然后使用最后一列显示结果。在索引中包含所有五列意味着 MySQL 不必 return 到主 table 中索引找到的各个行。它可以单独从索引满足查询,这意味着它可以从海量存储中顺序读取索引。在传统的旋转硬盘驱动器上,这意味着读取磁头不必从索引到 table 来回滴答滴答地移动到索引以满足查询。它要快得多。它被称为 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
优化器有两种方法在一定范围内执行索引查询:
方案一,使用索引:
- 进入项目开头的索引。
- 向前扫描直到范围结束。过滤掉与其他
WHERE
条件不匹配的所有行。
- 对于每个项目,请查看数据以获取所需的其他列。这是对磁盘的随机读取——可能没有缓存,等等。
方案二,忽略索引,扫描数据
- 扫描数据中的所有行,忽略任何不符合
WHERE
条件的行。
执行一种方法和执行另一种方法之间的分界点取决于大量统计数据等。通常在 table 的 10% 到 30% 之间。你注意到边界处有一个很大的跳跃;这是因为统计数据不是 'perfect'。这种跳跃可能是好的,也可能是坏的。
旁注。一旦您拥有 Ollie 的更好索引,分区就不会为您带来任何性能。事实上,它可能会减慢查询速度。
DOUBLE
(8 字节)对于 lat/lng/alt 是多余的。参见 my representation choices。
DOUBLE(8,3)
(还是8个字节)更糟;永远不要在 FLOAT
或 DOUBLE
.
上使用 (m,n)
平均值的平均值在数学上是不正确的。考虑保留总和和计数,然后计算 SUM(sum)/SUM(count)
以获得适当的 AVG
.
想以快 10 倍的速度获得每周结果吗?每天在摘要中构建和维护计数和总和 table。这会将数据缩小 1/144。然后通过总和等方式报告。A discussion on Summary Tables.
我正在寻找有关 MySQL table 上的索引如何工作的一些见解,因为我遇到了一些我不明白的问题。
让我们从我正在使用的table开始:
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 年 August/Sept/Oct 中的 select 数据。'readings' 在一天中均匀分布,并且始终在 10 分钟的边界上(即 10:10:00、10:20:00、10:30:00 等)从 2017 年 5 月起,每天 'readings' 的数量相当稳定,为 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;
为了获得一些指标,我开始 select 从 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 万行以查找该分区。在旁注中,间隔的确切位置并不重要。我也可以通过将间隔提前一个月来重现此问题。
如果有帮助,执行时间在'jump'之前返回的列是525644,加上1天后的数字是535004。
您的过滤条件是:
- 明确的分区选择
- 在
quality
上的相等匹配
time
的范围扫描
- 在
station_id
和channel_id
上的一组成对匹配。
处理标准 2 和 3 的索引就是您所需要的。将等式匹配列首先放入索引中,然后是范围扫描列,然后用查询所需的其他列对索引进行四舍五入以获得 covering index。
那个索引是(quality, time, station_id, channel_id, average)
为什么有效?查询规划器可以立即跳转到索引的第一个符合条件的行,因为它知道需要 quality
和起始 time
。然后它可以按顺序扫描索引,进行成对匹配并检索 average
列。 MySQL可以满足从索引开始的整个查询,省去了很多跳回table获取信息的过程,所以速度也很快。
您已经在 (channel_id,station_id,time,quality)
上建立了索引。您可能希望在创建新索引时删除该索引,因为看起来它的用途相似。
为什么查询规划器有时使用索引有时不用?这取决于很多事情,主要是查询规划器估计它是否需要使用索引做更少的工作或只扫描 table。索引和列包含基数估计——数据项中不同值的数量。这些基数是估计值,有时非常不准确。您有分区:这可能会导致查询计划程序以某种方式限制其选择。当查询规划器无法弄清楚要做什么时的回退就是你得到的:完整的 table 扫描。
您问题中提到的索引已经需要相当费力的索引扫描才能满足查询;我想当您更改日期戳范围时,查询计划器会切换到完整的 table 扫描策略。这对于操作基于 DBMS 的软件的人来说是一个麻烦:随着应用程序的增长,有时查询计划器会突然转向一个新的且效率较低的计划。您需要掌握突然的性能变化并添加索引。
专业提示:与构建更好的索引相比,询问为什么查询计划程序的选择通常是徒劳的。 (除非你的开发工作是在查询规划器上工作。)
我建议使用五列索引。您的查询使用四列进行过滤,然后使用最后一列显示结果。在索引中包含所有五列意味着 MySQL 不必 return 到主 table 中索引找到的各个行。它可以单独从索引满足查询,这意味着它可以从海量存储中顺序读取索引。在传统的旋转硬盘驱动器上,这意味着读取磁头不必从索引到 table 来回滴答滴答地移动到索引以满足查询。它要快得多。它被称为 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
优化器有两种方法在一定范围内执行索引查询:
方案一,使用索引:
- 进入项目开头的索引。
- 向前扫描直到范围结束。过滤掉与其他
WHERE
条件不匹配的所有行。 - 对于每个项目,请查看数据以获取所需的其他列。这是对磁盘的随机读取——可能没有缓存,等等。
方案二,忽略索引,扫描数据
- 扫描数据中的所有行,忽略任何不符合
WHERE
条件的行。
执行一种方法和执行另一种方法之间的分界点取决于大量统计数据等。通常在 table 的 10% 到 30% 之间。你注意到边界处有一个很大的跳跃;这是因为统计数据不是 'perfect'。这种跳跃可能是好的,也可能是坏的。
旁注。一旦您拥有 Ollie 的更好索引,分区就不会为您带来任何性能。事实上,它可能会减慢查询速度。
DOUBLE
(8 字节)对于 lat/lng/alt 是多余的。参见 my representation choices。
DOUBLE(8,3)
(还是8个字节)更糟;永远不要在 FLOAT
或 DOUBLE
.
(m,n)
平均值的平均值在数学上是不正确的。考虑保留总和和计数,然后计算 SUM(sum)/SUM(count)
以获得适当的 AVG
.
想以快 10 倍的速度获得每周结果吗?每天在摘要中构建和维护计数和总和 table。这会将数据缩小 1/144。然后通过总和等方式报告。A discussion on Summary Tables.