我有一个监控表,其结构如下:
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;
答案 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
是此有序列表中“下一行”的非常快速的操作。此外,由于value
与id
和time
在一起,因此无需额外的努力来获取值。
您无法更快地扫描请求的行。但它需要PRIMARY KEY
。 (好的,UNIQUE(id, time)
将被“提升”为PK,但我们不要混淆这个问题。)
对比...给定一个索引(time, id)
,它可以对日期进行精确扫描,但是它必须跳过id != 165
的所有条目但是它必须读取所有这些行发现他们不适用。更多的努力。
由于DISTINCT
不清楚你的意图是什么,我无法继续详细讨论它是如何发挥作用的。我只想说:已找到可能的行;现在需要进行某种二次传递来DISTINCT
。 (甚至可能不需要进行排序。)