多个表或使用分区?

我已经在多个线程上看到了几乎可以回答的问题,但没有考虑此特定域的含义:

我正在寻找将时间序列数据存储在MySQL中的大量量表(500个和不断增长的数据),每个量表每隔5分钟提供一个浮点值.最简单的结构是:
-gauge_id
-时间戳
-价值

(其中gauge_id和timestamp组合为主键)

这意味着每年每条规约有105120行-所有这些都需要存储10或20年.届时,对于1000个量规,我们每年将查看1亿条记录.

数据是成批写入的,通常每个通道的值都会从远程源聚合到XML文件中,并每小时或每天读入数据库.因此,每小时最多可以有我们的量规.

数据的读取操作将基于时间范围按规范进行(因此,规范之间不会进行数据的联接操作).所以例如获取两个日期之间的量规X的所有值.
通常,这还将包括某种形式的聚合/插值功能-因此用户可能希望查看任意范围的每日平均值或每周最大值等.
同样,读取次数相对较少,但是这些读取需要MySQL不到1秒的响应.

在这个阶段,我倾向于每个表1个表,而不是在MySQL上对gauge_id分区一个大表.
这样做的逻辑是,这样可以使分片更容易进行,简化备份,并且在任何阶段都存在数据错误时,实质上可以使量规更易于拆卸/重建.
代价是写和读操作都比较复杂.

有什么想法吗?

——–更新——–

我在MacBook 2.4gHz核心2 duo,4 gig ram上进行了一些测试.

设置下表:

CREATE TABLE `test` (
  `channel_id` int(10) NOT NULL,
  `time` datetime NOT NULL,
  `value` int(10) NOT NULL,
  KEY `channel_id` (`channel_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

用存储过程填充:

CREATE PROCEDURE `addTestData`(IN ID INT, IN RECORDS INT)
    BEGIN
        DECLARE i INT DEFAULT 1;
        DECLARE j DATETIME DEFAULT '1970-01-01 00:00:00';
           WHILE (i<=RECORDS) DO
              INSERT INTO test VALUES(ID,j,999);
              SET i=i+1;
              SET j= j + INTERVAL 15 MINUTE;
           END WHILE;
    END $$

然后我打电话来创建前一百万条记录

call addTestData(1,1000000);

插入在47秒内执行

SELECT * FROM `test` WHERE channel_id = 1 and YEAR(time) = '1970';

在0.0006秒内执行

SELECT AVG(value) as value, DATE(time) as date FROM `test` 
WHERE channel_id = 1 and YEAR(time) = '1970' group by date;

在4.6秒内执行(MAX,SUM功能同时执行).

再添加4个仪表后:

call addTestData(2,1000000);
call addTestData(3,1000000);
call addTestData(4,1000000);
call addTestData(5,1000000);

插入操作每47秒执行一次,表使用了78 MB

我运行相同的两个查询-并获得与表中一百万条记录完全相同的执行时间(较大的查询为4.6秒).

因此,除非将其用于分片,备份和将来由硬件驱动的对任何单个量规表的更改(即多个读数,数据间隔的更改)的潜在用途,在可预见的情况下似乎无需拆分为多象限表.甚至没有尝试使用分区来运行查询,似乎没有任何原因.

– – – – 然而 – – – – – – –

由于4.6秒的查询时间并不理想,因此我们显然需要进行一些优化.
第一步,我重组了查询:

SELECT 
    AVG(value) as value, 
    DATE(time) as date 
FROM 
    (SELECT * FROM test 
    WHERE channel_id = 1 and YEAR(time) = '1970') 
    as temp 
group by date;

在具有500万条记录(超过5个channel_id)的表上运行,查询需要4.3秒.
如果我在具有1个通道,100万条记录的表上运行它,则它将在0.36秒内运行!
我为此挠了一下头…

对500万条记录的表进行分区

ALTER TABLE test PARTITION BY HASH(channel_id) PARTITIONS 5;

随后也在0.35秒内完成上述复合查询,同样的性能提升.

解决方法:

对我来说,您的方案中没有任何东西可以证明按量规进行分区,如果在gauge_id上​​有索引,性能就不会成为问题,因为MySql会通过使用索引立即找到与特定量规相关的行,之后其他操作将是就像处理每个量规的专用表一样.

唯一可以进行分区的情况是,如果访问最新的量规数据(例如,最新的10%),则比旧数据(剩余的90%)更频繁地访问(如果是这样的话),则分为两个“最近”和“存档”表可能会给您带来很多性能优势.

如果您对单个表的操作不涉及索引,则对合并表的相同操作不应花费更长的时间,因为如果操作涉及索引,则MySql首先会使用gauge_id上​​的索引将结果缩小到某些表行您应该使索引成为合并表上以“ gauge_id”开头的多列索引,例如单个表上的INDEX(timestamp)应该变为INDEX(gauge_id,timestamp),然后在大多数情况下,该操作将与单个表花费相同的时间.同样,不要被“ 5亿行”之类的数字所困扰,数据库旨在处理该数量的数据.

我的言论几乎都是基于我每次遇到您的情况时的经验,并决定使用单个表,出于某种原因,我最终将表合并为一个表,因为在大多数情况下,项目已经成熟这是一个痛苦的过程.我确实经历过“关系数据库并非旨在像那样使用”.

我真的很想听听其他人对此的意见,顺便说一下,在进行任何一种测试之前,都要进行大量测试,MySql的工作量很多(unexpected behaviors).

上一篇:MySQL的“分区”与分裂数据到不同的表


下一篇:Partitioning with PostgreSQL v11 (转发)