我想问一个关于如何使用innodb引擎提高大型MySQL表格性能的问题:
目前我的数据库中有一个表,大约有2亿行。该表定期存储由不同传感器收集的数据。该表的结构如下:
CREATE TABLE sns_value (
value_id int(11) NOT NULL AUTO_INCREMENT,
sensor_id int(11) NOT NULL,
type_id int(11) NOT NULL,
date timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
value int(11) NOT NULL,
PRIMARY KEY (value_id),
KEY idx_sensor id (sensor_id),
KEY idx_date (date),
KEY idx_type_id (type_id) );
起初,我想过在几个月内对桌子进行分区,但由于新传感器的不断增加,它将在一个月内达到目前的尺寸。
我提出的另一个解决方案是通过传感器对表进行分区。但是,由于MySQL的1024个分区的限制不是一个选项。
我认为正确的解决方案是为每个传感器使用具有相同结构的表:
sns_value_XXXXX
这样,将有超过1000个表,估计每年大小为3000万行。同时,这些表可以在几个月内进行分区,以便最快地访问数据。
此解决方案会带来什么问题?有更规范化的解决方案吗?
使用其他信息进行编辑
我认为该表与我的服务器相比较大:
每个传感器可能有多种变量类型(CO,CO2等)。
我主要有两个慢查询:
1)每个传感器和类型的每日摘要(平均值,最大值,最小值):
SELECT round(avg(value)) as mean, min(value) as min, max(value) as max, type_id
FROM sns_value
WHERE sensor_id=1 AND date BETWEEN '2014-10-29 00:00:00' AND '2014-10-29 12:00:00'
GROUP BY type_id limit 2000;
这需要超过5分钟。
2)垂直到水平视图并导出:
SELECT sns_value.date AS date,
sum((sns_value.value * (1 - abs(sign((sns_value.type_id - 101)))))) AS one,
sum((sns_value.value * (1 - abs(sign((sns_value.type_id - 141)))))) AS two,
sum((sns_value.value * (1 - abs(sign((sns_value.type_id - 151)))))) AS three
FROM sns_value
WHERE sns_value.sensor_id=1 AND sns_value.date BETWEEN '2014-10-28 12:28:29' AND '2014-10-29 12:28:29'
GROUP BY sns_value.sensor_id,sns_value.date LIMIT 4500;
这也需要5分钟以上。
其他注意事项
假设“每个传感器的一个表”方法
更新02/02/2015
我们已经为每年的数据创建了一个新表,我们也每天对其进行分区。每个表有大约2.5亿行,有365个分区。使用的新索引是Ollie建议的(sensor_id,date,type_id,value),但查询仍需要30秒到2分钟。我们不使用第一个查询(每日摘要),只使用第二个查询(垂直于水平视图)。
为了能够对表进行分区,必须删除主索引。
我们错过了什么吗?有没有办法改善表现?
非常感谢!
答案 0 :(得分:1)
根据问题的更改进行编辑
每个传感器一张表确实是一个非常糟糕的主意。有几个原因:
我之前的这个答案版本建议按时间戳划分范围。但是,这不会与您的value_id
主键一起使用。但是,通过您显示的查询以及对表格进行正确索引,可能无法进行分区。
(如果可以的话,请避免使用列名date
:它是一个保留字,您在编写查询时会遇到很多麻烦。相反,我建议您使用ts
,这意味着时间戳。)
小心:int(11)
值对于value_id
列来说还不够大。你将失去ids。对该列使用bigint(20)
。
您提到了两个问题。即使您将所有值保存在单个表中,这两个查询都可以通过适当的复合索引非常高效。这是第一个。
SELECT round(avg(value)) as mean, min(value) as min, max(value) as max,
type_id
FROM sns_value
WHERE sensor_id=1
AND date BETWEEN '2014-10-29 00:00:00' AND '2014-10-29 12:00:00'
GROUP BY type_id limit 2000;
对于此查询,您首先使用常量查找sensor_id
,然后查找一系列date
值,然后按以下方式进行聚合:\ n type_id
。最后,您要提取value
列。因此,(sensor_id, date, type_id, value)
上的所谓compound covering index将能够通过索引扫描直接满足您的查询。这应该对你来说非常快 - 即使有一张大桌子,肯定要快5分钟。
在第二个查询中,类似的索引策略将起作用。
SELECT sns_value.date AS date,
sum((sns_value.value * (1 - abs(sign((sns_value.type_id - 101)))))) AS one,
sum((sns_value.value * (1 - abs(sign((sns_value.type_id - 141)))))) AS two,
sum((sns_value.value * (1 - abs(sign((sns_value.type_id - 151)))))) AS three
FROM sns_value
WHERE sns_value.sensor_id=1
AND sns_value.date BETWEEN '2014-10-28 12:28:29' AND '2014-10-29 12:28:29'
GROUP BY sns_value.sensor_id,sns_value.date
LIMIT 4500;
同样,您从sensor_id
的常量值开始,然后使用date
范围。然后,您提取type_id
和value
。这意味着我提到的相同的四列索引应该适合你。
CREATE TABLE sns_value (
value_id bigint(20) NOT NULL AUTO_INCREMENT,
sensor_id int(11) NOT NULL,
type_id int(11) NOT NULL,
ts timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
value int(11) NOT NULL,
PRIMARY KEY (value_id),
INDEX query_opt (sensor_id, ts, type_id, value)
);
答案 1 :(得分:0)
为一系列传感器创建单独的表是一个想法。
如果您不需要,请不要将auto_increment用于主键。通常,数据库引擎的主键为clustering the data。
使用复合键,取决于您的用例,列的顺序可能不同。
编辑:还将类型添加到PK中。考虑到查询,我会这样做。选择字段名称是有意的,它们应该是描述性的,并且始终考虑保留的单词。
CREATE TABLE snsXX_readings (
sensor_id int(11) NOT NULL,
reading int(11) NOT NULL,
reading_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
type_id int(11) NOT NULL,
PRIMARY KEY (reading_time, sensor_id, type_id),
KEY idx date_idx (date),
KEY idx type_id (type_id)
);
另外,请考虑总结读数或将它们分组到一个字段中。
答案 2 :(得分:0)
您可以尝试获取随机化摘要数据
我有类似的表。表引擎myisam(最小表大小),10m记录,我的表上没有索引因为无用(已测试)。获取所有数据的所有范围。结果:10sn这个查询。
SELECT * FROM (
SELECT sensor_id, value, date
FROM sns_value l
WHERE l.sensor_id= 123 AND
(l.date BETWEEN '2013-10-29 12:28:29' AND '2015-10-29 12:28:29')
ORDER BY RAND() LIMIT 2000
) as tmp
ORDER BY tmp.date;
第一步的查询在日期和排序之间获得随机化的第一个2k数据,在第二步排序数据。查询每次获得不同数据的2k结果。