2014-10-28 71 views
4

我想问一个有关如何在使用InnoDB引擎大MySQL表提高性能的问题:提高性能的一大MySQL表

中目前我的数据库有大约200万行的表。该表格定期存储由不同传感器收集的数据。该表的结构如下:

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万的估计大小每年行数。同时,这些表格可以在几个月内进行分区,以便最快速地访问数据。

该解决方案会产生哪些问题?是否有更规范的解决方案?

编辑附加信息

我认为表是关于大到我的服务器:

  • 云2xCPU和8GB内存
  • LAMP(CentOS的6.5和MySQL 73年5月1日)

每个传感器可能有多个变量类型(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分钟。

其他考虑

  1. 时间戳可能由于插入件的特性进行重复。
  2. 定期插入必须与选择共存。
  3. 没有更新或删除在表上执行。

的假定的制造“一个表中的每个传感器”的方法

  1. 表针对每个传感器会小得多,以便访问会更快。
  2. 每个传感器只能在一张桌子上进行选择。
  3. 选择来自不同传感器的混合数据对时间要求不高。

更新2015年2月2日

我们已经创建了一个新表,每年的数据,这是我们也每天划分为。每张桌子大约有2.5亿行,有365个分区。使用的新索引与Ollie建议的(sensor_id,date,type_id,value)一样,但查询仍然需要30秒到2分钟。我们不使用第一个查询(每日摘要),仅使用第二个查询(垂直到水平视图)。

为了能够对表格进行分区,必须删除主索引。

我们错过了什么吗?有没有办法提高性能?

非常感谢!

+0

当前结构发生了什么问题? – 2014-10-28 17:57:08

+0

大?这里很大吗? – TomTom 2014-10-28 18:16:12

+0

teis数据的用途是什么?你应该怎么读? – Aret 2014-10-28 19:28:18

回答

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) 
); 

此外,请考虑汇总读数或将它们分组为单个字段。

+1

关于避免表分离和对主键建议完全不正确,您是正确的。如果有的话,对于MySQL和InnoDB,PK应该始终是auto_increment,并且几乎不会复合或复合。原因在于你提到的非常集群,以及InnoDB使用PK进行集群的方式。 – 2014-10-28 20:58:28

+0

感谢您纠正我关于主键的问题,您可以参考一些关于InnoDB和PK用法的材料吗? – Aret 2014-10-28 21:48:27

+1

Percona有许多有趣的文章。我不能给你几个包含所有内容的链接,但[This pdf](http://www.percona.com/files/presentations/percona-live/london-2011/PLUK2011-b-tree- index-and-innodb.pdf)应该在这个问题上提出一些亮点。就我个人而言,我一直在阅读文章并涉猎InnoDB源代码以掌握它的功能。简单地说,下一个PK值应该总是较大,以避免B树重新平衡。基本上,auto_increment这样做,所以它几乎总是理想的候选人。 – 2014-10-28 22:00:58

1

编辑根据变化的问题每个传感器

一个表,相对于,一个非常糟糕的主意确实如此。有几个原因:在普通的操作系统

  1. 的MySQL服务器已经很难与万的表。大多数操作系统无法同时处理多个同时进行的文件访问。
  2. 每次添加(或删除)传感器时都必须创建表格。
  3. 涉及来自多个传感器的数据的查询将变得缓慢且令人费解。

我以前版本的答案建议按时间戳分区。但这不适用于您的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值,那么你被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_idvalue。这意味着我提到的相同的四列索引应该为你工作。

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

@DRapp行交换索引的原因与MySQL如何使用复合索引来满足查询有关。当我建议这两个索引时,最初的提问者还没有披露正在使用的查询。 – 2014-10-29 17:08:01

+0

了解,谢谢 – DRapp 2014-10-29 17:28:55

+0

为什么要将tehe值添加到索引中? – Aret 2014-10-30 05:39:13

0

你可以尝试得到随机的汇总数据

我有类似的表。表引擎myisam(最小的表大小),10米记录,因为无用(测试)我的桌子上没有索引。获取所有数据的所有范围。结果: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结果为不同的数据。