【问题标题】:MySQL Large Table Sharding to Smaller Table based on Unique IDMySQL大表分片到基于唯一ID的小表
【发布时间】:2019-01-30 09:50:50
【问题描述】:

我们有一个包含以下列的大型 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;

【问题讨论】:

  • 你考虑过串行分区吗?手动:dev.mysql.com/doc/refman/5.7/en/partitioning.html
  • @fifonik - “串行”??那是什么? This 显示类型,但唯一有用的是BY RANGE
  • 让我们检查更多的东西 -- 多少 RAM?现在表中有多少 GB?当你停止收集数据时有多少? innodb_buffer_pool_size 的值是多少?
  • 我的意思是PARTITION BY HASH(func())。在最简单的情况下, func 可能是 crc32(left(serial_number, 10)) 或更有用的东西,因为我不知道您的序列号格式,所以我无法建议。无论如何,我不会进行分片,因为以后支持数百万个表可能会很痛苦(想象你想在那里添加/更改列或在一个查询中获取多个序列的数据)。
  • @RickJames 现在我们在 16GB 中设置了大约 12GB 的 RAM。当表达到大约 100M 行并且表大小达到大约 10GB(即 innodb_buffer_pool_size 开始用完)时,这很容易变满。

标签: mysql database database-design


【解决方案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

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

其他评论:

  • 很少有使用案例可以通过分区实际加快处理速度。
  • 制作大量“相同”的表是维护的噩梦,同样,也不是性能优势。可能有一百个关于 stackoverflow 的 Q&A 大喊不要这样做。
  • 通过在PRIMARY KEY 中拥有serial_numberfirst,所有引用单个序列号的查询都可能受益。
  • 一百万serial_numbers?没问题。
  • 分区的一个常见用例涉及清除“旧”数据。这是因为大的DELETEsDROP PARTITION 更昂贵。这涉及PARTITION BY RANGE(TO_DAYS(dt))。如果您对此感兴趣,我的 PK 建议仍然有效。 (无论有没有这种分区,相关查询的运行速度都差不多。)
  • 在表的容量超过您的磁盘之前需要多少个月? (如果这是一个问题,让我们讨论一下。)
  • 你需要8字节的DOUBLE吗? FLOAT 具有大约 7 位有效数字的精度,并且只占用 4 个字节。
  • 正在使用 InnoDB?
  • serial_number 是否固定为 20 个字符?如果没有,请使用VARCHAR。另外,CHARACTER SET ascii 可能比默认的 utf8 更好?
  • 每个表(或表的每个分区)至少涉及一个操作系统必须处理的文件。当你有“太多”时,操作系统会抱怨,通常在 MySQL 抱怨之前。 (药物过量都很难“死”。)

【讨论】:

  • 我相信按序列号分区会增加查询执行时间(需要基准测试)。 “这里列出了分区的一些优点: - 由于满足给定 WHERE 子句的数据只能存储在一个或多个分区上,这会自动从搜索中排除任何剩余的分区,因此可以极大地优化某些查询。” dev.mysql.com/doc/refman/5.7/en/partitioning-overview.html
  • 同意尽可能减少行存储需求的建议(使用 varchar 和序列排序规则,浮点数)。
  • @fifonik - 概括:如果没有足够的索引,分区修剪可以提供显着的性能改进。有了足够的索引,分区修剪可能会稍微降低性能。你有一个足够的索引:任何以(serial_number, dt) 开头的索引。手册倾向于指定什么你能做什么,而不是是否去做。
  • @fifonik - 另一点:切换到(或从)分区时,需要重新考虑所有索引。一种情况下的最佳索引可能在另一种情况下很差。
  • @fifonik - 如果您提供SHOW CREATE TABLE,我可能会提供有关减少表及其索引的磁盘占用空间的更多提示。哦,而且,分区表本身就需要额外的空间。
【解决方案2】:

解决查询

 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 十进制(8,5)`。这将总共节省每行 3 个字节。

【讨论】:

  • CMIIW 将device_sndt 作为 PK 而不仅仅是一个索引会比基于device_sn 的分区更有效吗?假设我是否会有许多使用条件 device_sn 和 dt 运行的查询。
  • @HeruS - PK 很可能比分区更好。特别是,您提供的一个查询将在 PK 中运行得更好。如果您希望提供查询示例;我们可以进一步讨论。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-03-17
  • 2016-04-27
  • 2023-04-03
  • 1970-01-01
  • 2020-06-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多