2017-10-20 248 views
1

我正在寻找一些有关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年的“解读”八月/九月/十月选择数据通过一天均匀地分布,并总是在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

有多少百分比的数据具有质量= 1? –

回答

2

您的筛选标准是:

  1. 明确分区选择
  2. quality
  3. 范围扫描相等匹配上time
  4. 成对匹配上束和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不必返回主表中索引找到的各行。它可以单独满足来自索引的查询,这意味着它可以从海量存储中顺序读取索引。在传统的旋转硬盘驱动器上,这意味着读取磁头不必为了满足查询而从索引到表格来回查询。它快得多。它被称为covering index

专业提示:使用BETWEEN作为datestamp范围是一个错误。代替使用

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 
+0

非常有魅力,thx。我想知道为什么MySQL决定停止在我原来的问题中使用现有的索引。我确实在某个地方看过,当它需要检查的行大约占总数的30%时,它突然停止使用索引,但我不知道这是否属实。而且,为什么在WHERE子句中没有使用平均值时,平均值会添加到索引中? – Lieuwe

+0

请参阅我的编辑。 –

1

优化有两种方式在一定范围内进行索引查询:

选项1,使用索引:在项目的开始

  1. 河段入索引。
  2. 向前扫描直到范围结束。筛选出不符合其他WHERE条件的行。
  3. 对于每个项目都会覆盖数据以获取所需的其他列。这是一个随机读入磁盘 - 可能没有缓存等。

选项2,忽略索引并扫描数据。

  1. 扫描数据中的所有行,忽略任何与WHERE标准不匹配的行。

做一个方法和做另一个方法之间的截断取决于大量的统计数据等。它通常在表的10%和30%之间。你注意到边界有一个很大的跳跃;这是因为统计不是“完美”的。这种跳跃可以是更好的或更糟糕的。

附注。一旦你有奥利的更好的索引,分区购买你没有表现。事实上,它可能会降低查询速度。

DOUBLE(8字节)为lat/lng/alt是矫枉过正。见my representation choices

DOUBLE(8,3)(还有8个字节)更差;请勿在FLOATDOUBLE上使用(m,n)

平均数的平均值在数学上不正确。考虑保留一笔钱和一个计数,然后计算SUM(sum)/SUM(count)以得到一个合适的AVG

想要获得每周结果10倍的速度?在汇总表中构建和维护日常计数和总和。那会使数据缩小1/144。然后通过汇总总和等来报告A discussion

+0

感谢您的反馈,不幸的是,表格和检索不是我的。使用的时间间隔是任意的,可以是2天,也可以是3个月。平均值实际上是在数据插入时计算的(它是包括当前数据在内的最后X个读数的平均值)并且是正确的 - 他们只是需要将它舍入到我估计的2位小数。你确定分区没有任何区别吗?引用的例子是在一台测试机器上,在现有的服务器上,我们每年有数百万的读数可以追溯到二十年(如果间隔在分区边界上,我显然会说明这一点)。 – Lieuwe