高流量表,最佳索引?

时间:2017-02-17 21:44:58

标签: mysql mariadb

我有一个监控表,其结构如下:

CREATE TABLE `monitor_data` (
    `monitor_id` INT(10) UNSIGNED NOT NULL,
    `monitor_data_time` INT(10) UNSIGNED NOT NULL,
    `monitor_data_value` INT(10) NULL DEFAULT NULL,
    INDEX `monitor_id_data_time` (`monitor_id`, `monitor_data_time`),
    INDEX `monitor_data_time` (`monitor_data_time`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB;

这是一个非常高的流量表,每分钟可能有数千行。每行属于一个监视器,包含一个值和时间(unix_timestamp)

我有三个问题:

1。 突然,经过几个月的开发,桌子突然变得很慢。先前在一秒钟内完成的查询现在可能需要一分钟。我在my.cnf中使用标准设置,因为这是一个开发机器,但我的行为确实很奇怪。

2。 我不确定我有最佳索引。 “普通”查询如下所示:

SELECT DISTINCT(md.monitor_data_time), monitor_data_value
FROM monitor_data md 
WHERE md.monitor_id = 165
    AND md.monitor_data_time >= 1484076760
    AND md.monitor_data_time <= 1487271199
ORDER BY md.monitor_data_time ASC;

以上查询的EXPLAIN如下所示:

id;select_type;table;type;possible_keys;key;key_len;ref;rows;Extra
1;SIMPLE;md;range;monitor_id_data_time,monitor_data_time;monitor_id_data_time;8;\N;149799;Using index condition; Using temporary; Using filesort

您如何看待这些指数?

3。 如果我在上面的查询中省略了DISTINCT,即使表中没有任何重复的行,我实际上也会获得重复的行。对此行为的任何解释?

非常感谢任何输入!

更新1:

关于表格结构的新建议:

CREATE TABLE `monitor_data_test` (
`monitor_id` INT UNSIGNED NOT NULL,
`monitor_data_time` INT UNSIGNED NOT NULL,
`monitor_data_value` INT UNSIGNED NULL DEFAULT NULL,
PRIMARY KEY (`monitor_data_time`, `monitor_id`),
INDEX `monitor_data_time` (`monitor_data_time`)
) COLLATE='utf8_general_ci' ENGINE=InnoDB;

2 个答案:

答案 0 :(得分:2)

您如何看待这些索引?

(monitor_id,monitor_data_time)上的索引似乎适合查询。这适用于索引范围扫描操作,可以非常快速地消除需要检查的大量行。

更好的是覆盖索引,其中还包含monitor_data_value列。然后可以完全从索引中满足查询,而无需从数据表中查找页面以获取monitor_data_value

甚至更好的是将InnoDB集群密钥作为列上的PRIMARY KEY或UNIQUE KEY,而不是在未定义适当的索引时产生InnoDB创建的合成行标识符的开销。

如果我不允许重复的(monitor_id, monitor_data_time)元组,那么我将在那些不可为空的列上定义带有UNIQUE索引的表。

 CREATE TABLE `monitor_data` 
 ( `monitor_id`         INT(10) UNSIGNED NOT NULL
 , `monitor_data_time`  INT(10) UNSIGNED NOT NULL
 , `monitor_data_value` INT(10) NULL DEFAULT NULL
 , UNIQUE KEY `monitor_id_data_time` (`monitor_id`, `monitor_data_time`)
 ) ENGINE=InnoDB

或等效,指定PRIMARY代替UNIQUE并删除标识符

 CREATE TABLE `monitor_data` 
 ( `monitor_id`         INT(10) UNSIGNED NOT NULL
 , `monitor_data_time`  INT(10) UNSIGNED NOT NULL
 , `monitor_data_value` INT(10) NULL DEFAULT NULL
 , PRIMARY KEY (`monitor_id`, `monitor_data_time`)
 ) ENGINE=InnoDB

对此行为的任何解释?

如果查询(显示在问题中)使用DISTINCT关键字返回不同的行数,那么表中的必须是重复的(monitor_id,monitor_data_time,monitor_data_value)元组。表定义中没有任何内容可以保证我们没有重复项。

还有其他一些可能的解释,但这些解释都与添加/更改/删除的行以及查看不同快照,事务隔离级别,yada,yada的查询有关。如果数据没有变化,那么就有重复的行。

PRIMARY KEY约束(或UNIQUE KEY约束非可空列)将保证我们的唯一性。

请注意,DISTINCT是SELECT列表中的关键字。这不是一个功能。 DISTINCT关键字适用于SELECT列表中的所有表达式。 md.monitor_date_time周围的parens是多余的。

退出DISTINCT关键字将不再需要“使用filesort”操作。对于大型集合来说这可能是昂贵的,特别是当集合太大而无法在内存中排序时,排序必须溢出到磁盘。

保证唯一性,省略DISTINCT关键字,并按索引顺序返回行,最好是群集密钥,效率会更高。

此外,辅助索引monitor_data_time不会使此查询受益。 (可能有其他查询可以有效地使用索引,但有人怀疑这些查询也会有效地使用以monitor_data_time作为前导列的复合索引。

答案 1 :(得分:2)

SELECT DISTINCT(md.monitor_data_time), monitor_data_value

相同
SELECT DISTINCT md.monitor_data_time, monitor_data_value

也就是说,这一对是截然不同的。 仅对time进行重复数据删除。这就是你想要的吗?

如果您尝试删除只是 time,请执行以下操作

SELECT time, AVG(value)
    ...
    GROUP BY time;

为了获得最佳性能

WHERE md.monitor_id = 165
AND md.monitor_data_time >= 14840767604 ...

你需要

PRIMARY KEY (monitor_id, monitor_data_time)

并且必须按顺序排列。相反的顺序是很多不太有用。指导原则是:从'='开始,然后转到'范围'。更多讨论here

您有40亿monitor_id个值吗? INT占用4个字节;考虑使用较小的数据类型。

您是否有其他需要优化的查询?收集所有重要查询后,最好设计索引。

为什么PK

在InnoDB中,PRIMARY KEY与数据“聚集”在一起。也就是说,数据是三元组的有序列表:(id, time, value)存储在B +树中。查找id = 165 AND time = 1484076760是BTree的基本操作。并且非常快。然后向前扫描(这是“B +树”的“+”部分)直到time = 1487271199是此有序列表中“下一行”的非常快速的操作。此外,由于valueidtime在一起,因此无需额外的努力来获取值。

您无法更快地扫描请求的行。但它需要PRIMARY KEY。 (好的,UNIQUE(id, time)将被“提升”为PK,但我们不要混淆这个问题。)

对比...给定一个索引(time, id),它可以对日期进行精确扫描,但是它必须跳过id != 165的所有条目但是它必须读取所有这些行发现他们不适用。更多的努力。

由于DISTINCT不清楚你的意图是什么,我无法继续详细讨论它是如何发挥作用的。我只想说:已找到可能的行;现在需要进行某种二次传递来DISTINCT。 (甚至可能不需要进行排序。)