提高大型MySQL表的性能

时间:2014-10-28 17:52:39

标签: mysql sql-server innodb

我想问一个关于如何使用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万行。同时,这些表可以在几个月内进行分区,以便最快地访问数据。

此解决方案会带来什么问题?有更规范化的解决方案吗?

使用其他信息进行编辑

我认为该表与我的服务器相比较大:

  • 云2xCPU和8GB内存
  • LAMP(CentOS 6.5和MySQL 5.1.73)

每个传感器可能有多种变量类型(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. 不对表格执行更新或删除。
  4. 假设“每个传感器的一个表”方法

    1. 每个传感器的表格会小得多,因此访问速度会更快。
    2. 每个传感器仅在一张桌子上执行选择。
    3. 选择来自不同传感器的混合数据不是时间关键的。
    4. 更新02/02/2015

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

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

      我们错过了什么吗?有没有办法改善表现?

      非常感谢!

3 个答案:

答案 0 :(得分:1)

根据问题的更改进行编辑

每个传感器一张表确实是一个非常糟糕的主意。有几个原因:

  1. 普通操作系统上的MySQL服务器很难有数千个表。大多数操作系统无法同时处理多个同时进行的文件访问。
  2. 每次添加(或删除)传感器时,您都必须创建表格。
  3. 涉及来自多个传感器的数据的查询将是缓慢而复杂的。
  4. 我之前的这个答案版本建议按时间戳划分范围。但是,这不会与您的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_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)
    );
    

答案 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结果。