MySQL大表基于唯一ID分片到小表

时间:2019-01-21 03:26:35

标签: mysql database database-design

我们有一个很大的MySQL表(device_data),其中包含以下列:

ID (int)
dt (timestamp)
serial_number (char(20))
data1 (double)
data2 (double)
... // other columns

该表每天接收约1000万行。

我们已经根据时间戳记日期(device_data_YYYYMMDD)分开了表格,从而进行了分片。但是,我们认为这并不有效,因为我们的大多数查询(如下所示)始终检查“ serial_number”,并且会在许多日期执行。

SELECT * FROM device_data WHERE serial_number = 'XXX' AND dt >= '2018-01-01' AND dt <= '2018-01-07';

因此,我们认为基于序列号创建分片会更有效。基本上,我们将有:

device_data_<serial_number>
device_data_0012393746
device_data_7891238456

因此,当我们要查找特定设备的数据时,我们可以轻松地引用为:

SELECT * FROM device_data_<serial_number> WHERE dt >= '2018-01-01' AND dt <= '2018-01-07';

这种方法似乎是有效的,因为:

  1. 应用程序始终将首先基于设备访问数据。
  2. 我们检查了没有查询而没有先指定设备序列号的数据。
  3. 每个设备的表将相对较小(每天9000行)

我们认为我们将面临的一些挑战是:

  1. 我们有很多设备。这意味着表device_data_也将很多。我已经检查过MySQL在数据库中的表数上没有提供限制。与将它们放在一张桌子中相比,这会影响性能吗?
  2. 以后要扩展MySQL(例如使用主/从等)时,这会对以后产生什么影响?
  3. 还有其他解决方案/解决方案吗?

更新。以下是现有表中的show create table结果:

CREATE TABLE `test_udp_new` (
 `id` int(20) unsigned NOT NULL AUTO_INCREMENT,
 `dt` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
 `device_sn` varchar(20) NOT NULL,
 `gps_date` datetime NOT NULL,
 `lat` decimal(10,5) DEFAULT NULL,
 `lng` decimal(10,5) DEFAULT NULL,
 PRIMARY KEY (`id`),
 KEY `device_sn_2` (`dt`,`device_sn`),
 KEY `dt` (`dt`),
 KEY `data` (`data`) USING BTREE,
 KEY `test_udp_new_device_sn_dt_index` (`device_sn`,`dt`),
 KEY `test_udp_new_device_sn_data_dt_index` (`device_sn`,`data`,`dt`)
) ENGINE=InnoDB AUTO_INCREMENT=44449751 DEFAULT CHARSET=latin1 ROW_FORMAT=DYNAMIC

运行最频繁的查询:

SELECT  *
    FROM  test_udp_new
    WHERE  device_sn = 'xxx'
      AND  dt >= 'xxx'
      AND  dt <= 'xxx'
    ORDER BY  dt DESC;

2 个答案:

答案 0 :(得分:1)

处理 查询的最佳方法是使用

在未分区的表中
INDEX(serial_number, dt)

更妙的是更改PRIMARY KEY。假设您当前拥有id AUTO_INCREMENT,因为没有适合作为“自然PK”的列的唯一组合,

PRIMARY KEY(serial_number, dt, id),  -- to optimize that query
INDEX(id)  -- to keep AUTO_INCREMENT happy

如果还有其他经常运行的查询,请提供它们;这可能会伤害他们。在大型表中,找到最佳索引是一项艰巨的任务。

其他评论:

  • 在极少数情况下,分区实际上可以加快处理速度。
  • 制作许多“相同”表是维护的噩梦,同样,这也不是性能上的好处。关于堆栈溢出的问题可能有一百个问答,请不要这样做。
  • 通过在serial_number中拥有PRIMARY KEY first ,所有引用单个序列号的查询都可能会受益。
  • 一百万serial_numbers?没问题。
  • 一个常见的分区用例涉及清除“旧”数据。这是因为大DELETEs的成本比DROP PARTITION高得多。这涉及PARTITION BY RANGE(TO_DAYS(dt))。如果您对此感兴趣,我的PK建议仍然有效。 (有问题的查询在有或没有此分区的情况下都将以相同的速度运行。)
  • 表超出磁盘空间多少个月? (如果这是一个问题,让我们讨论一下。)
  • 您需要8字节的DOUBLE吗? FLOAT的精度约为7个有效数字,仅占用4个字节。
  • 使用InnoDB吗?
  • serial_number是否固定为20个字符?如果不是,请使用VARCHAR。另外,CHARACTER SET ascii可能比utf8的默认值更好?
  • 每个表(或表的每个分区)都包含至少一个OS必须处理的文件。如果“太多”,则OS发出gro吟声,通常在MySQL发出ans吟声之前。 (很难使过量的任何一个“死亡”。)

答案 1 :(得分:0)

解决查询

 PRIMARY KEY (`id`),
 KEY `device_sn_2` (`dt`,`device_sn`),
 KEY `dt` (`dt`),
 KEY `data` (`data`) USING BTREE,
 KEY `test_udp_new_device_sn_dt_index` (`device_sn`,`dt`),
 KEY `test_udp_new_device_sn_data_dt_index` (`device_sn`,`data`,`dt`)

->

 PRIMARY KEY(`device_sn`,`dt`, id),
 INDEX(id)
 KEY `dt_sn` (`dt`,`device_sn`),
 KEY `data` (`data`) USING BTREE,

注意:

  • 通过以device_sn, dt开始PK,您可以获得使用WHERE device_sn = .. AND dt BETWEEN ...进行查询的聚类优势
  • INDEX(id)是为了让AUTO_INCREMENT开心。
  • 拥有INDEX(a,b)时,INDEX(a)是多余的。
  • (20)是没有意义的; id最多将达到40亿。
  • 我扔了最后一个索引,因为新的PK可能对它有足够的帮助。
  • lng decimal(10,5)-不需要在小数点后第5个小数位;只需要3或2。所以:lat decimal(7,5), lng decimal(8,5)`。每行总共将节省3个字节。