mysql - MySQL 操作开销和处理大表
问题描述
这是我当前的表 DDL。它是一个 7TB 的大表,其中 6 TB 是数据,1TB 是索引。
该表中的记录数为 30 亿。
目前表上有 1000 万个插入,这是仅追加表,没有更新或删除。
我们每天从应用程序中进行 5000 万次选择。
CREATE TABLE `app_uses` (
`ID` varchar(36) NOT NULL,
`ACTION` varchar(255) NOT NULL,
`EVENT` varchar(255) NOT NULL,
`CUST_ID` varchar(36) DEFAULT NULL,
`VALUE` longtext,
`OBJECT` varchar(255) NOT NULL,
`DATE_TIME` datetime(6) DEFAULT NULL,
`GROUP` varchar(36) DEFAULT NULL,
`DISPLAY_NAME` varchar(2001) DEFAULT NULL,
`OBJ_ID` varchar(36) DEFAULT NULL,
`USER_DISPLAY` varchar(1500) DEFAULT NULL,
`USER_ID` varchar(36) DEFAULT NULL,
`NOTICE` varchar(1000) DEFAULT NULL,
`ALERT` varchar(4000) DEFAULT NULL,
PRIMARY KEY (`ID`),
KEY `IDX_APP_USES_CID_OT` (`CUST_ID`,`OBJECT`) USING BTREE,
KEY `IDX_APP_USES_OBJ_ID` (`OBJ_ID`) USING BTREE,
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
应用程序性能是否良好,除了一个操作问题外没有其他问题。我们无法执行更改表(添加新的可空列),我们必须使用需要超过 1 周的 percona 工具。
存储 XML 文件的 VALUE 和 ALERT 列也有 3 TB 的存储贡献。
现在的讨论是我们是否可以将这两列存储到另一个表中,这样原始表将轻 3 TB,但我们仍然无法非常快速地执行 Alter table。
所以问题是我们应该做这个拆分吗?
- 如果我们不这样做,如果这张表将来增长,我们会遇到什么问题?
- 如果我们这样做除了让这张桌子更轻之外,我们还会得到什么好处?
- 如果这张表在未来两年增长到 25 TB,我们可以开始遇到任何与数据库相关的问题吗?
迁移和破坏此表将花费很长时间且非常困难,因此在尝试此操作之前,如果值得付出如此多的努力,我们将对其进行精简。
我们正在使用 Aurora 并行查询,因此我们无法对该表进行分区,因为 Aurora 并行查询不适用于分区表
我们还必须在我们的应用程序中进行更改以适应损坏的表更改。
如果将所有数据保存在同一张表中,我们有什么好处吗?
解决方案
UUID 效率低下
将 UUID 与大型表混合时,您将遇到性能灾难。
即使是通过主键获取一行的简单操作通常也会涉及磁盘命中。这增加了很多 I/O —— 可能太多了,无法容忍。
50M 选择/天 = 600/秒。您的磁盘系统可以处理近 600 次读取/秒吗?
非记录
另一个问题——有很多“大”列。而且,平均每行 2KB,可能有很多行使用“非记录”存储。这涉及另一个磁盘命中。是什么ROW_FORMAT
?
部分缓解此问题的方法是确保避免SELECT *
并仅指定您实际需要的列。这可以避免一些额外的磁盘命中。
这个建议特别适用于VALUE
和ALERT
。
懒惰评估
如果选择具有未充分索引的过滤(WHERE
、、LIMIT
等),您可能正在获取那些庞大的列,只是在过滤时丢弃它们。
有时可以通过构建一个执行过滤的派生表来避免额外的 I/O,而让外部表SELECT
只获取最少数量的列。(向我们展示您正在使用的重要查询;我们可以进一步讨论。)
收缩 UUID
您使用的是什么类型的 UUID?如果它是“类型 1”,就像 MySQL 使用的那样,可以重新排列 id 的位以使它们大致按时间顺序排列;这对某些查询有很大帮助。
89f7eecd-a2ac-11eb-a9c1-5c80b6213dd8
^ This digit is the "type"
详情:http: //mysql.rjweb.org/doc.php/uuid
即使没有这个,36 字节的 UUID 也可以很容易地缩小到 16 字节,以放入 aBINARY(16)
而不是 38 字节用于您拥有的内容(36 用于字符串,2 用于不必要的VAR
.)
上面的链接讨论了这种收缩。此外,8.0 内置了必要的功能。
压缩
(我不赞成 InnoDB 的压缩,所以就不提了。反正提供超过 2x 压缩的可能性不大。)
如果您压缩这些 XML 字符串,它们将缩小(大约)3 倍。这将节省大约 2TB。
但是在客户端进行压缩(和解压缩);这减轻了服务器的负担并减少了客户端和服务器之间的带宽。
XML 是一种表示数据的庞大方式,但重新格式化它可能并非易事。(因此,我只提到压缩。)
注意,压缩后的列应该是VARBINARY(...)
or ...BLOB
,而不是文本类型。
对于“文本”且通常为“大”的其他列也是如此。
拆分表
我不认为拆分这两个庞大的列有任何显着的好处或缺点。它们“不公开”的事实意味着 InnoDB 已经提供了您提出的大部分好处。(MyISAM 将从您的拆分中受益。但不要使用该引擎。)
这是拆分表的一些好处,但可能没有足够的好处(在您的情况下)来保证更改。如果新表也有一个 uuid 作为 PK,则尤其如此,即使它与当前 ID 相同。
10M 插入
1000 万条 1 行INSERT
语句?这将类似于您当前设计的 30M 磁盘命中。您的磁盘每秒可以支持多少次写入?
我会建议像“批量”插入这样的事情。INSERT
具有 100 行的单个通常运行速度是 100 个单行插入的 10 倍。但是有 3 个随机命中 - 一个用于 PK,一个用于每个二级索引。所以,我不知道这 10 倍的加速是否真的会发生在你的情况下。
内存
RAM 越多越好。比如说,有了 7TB 的 RAM(未来是 25TB),我所说的大部分内容都会消失。但这在今天是不切实际的。因此,我正在推动缩小表大小、避免 UUID 或使它们按时间顺序排列(如果有用的话)等。
如果 5% 的基于 uuid 的索引可以放入 buffer_pool,那么 95% 的选择将需要命中磁盘。这是我很多讨论背后的原则。
注意:PK 是一个索引,但包含所有数据。
注意:通过二级索引查找涉及两次 BTree 查找。如果每个都基于一个 uuid,那么很有可能会发生两次磁盘命中。
注意:您的两表方法将涉及 2 次查找。每个都可能低于上面的“95%”,但仍然如此。
分区和并行查询
Aurora 在这方面领先于 MySQL(和 MariaDB)。但是,仍然没有太大的好处。
将每个分区放入不同分区的努力消除了较浅 BTree 的好处。(它甚至可能减慢速度。)
如果您受 CPU 限制,并行查询会有所帮助。但我预测你是 I/O-bound 并且会在 25TB 时崩溃。每个并行查询将花费大部分时间等待从磁盘读取块。
我假设每天 50M 的选择来自不同的连接?其中很多是“同时”发生的?这使您可以“并行”执行查询。我认为 Aurora 的“并行查询”是针对单一的、复杂的、SELECT
可以从多个线程同时执行部分任务中受益的。
一种PARTITIONing
可以受益的方法是当您需要二维索引时。例如: WHERE some date range AND some other test
。通过在“其他测试”的帮助下按日期进行PRIMARY KEY
分区,“分区修剪”选择要查看的分区,然后 PK 更快地到达所需的行。(这似乎不是您的用例。)
您的主要查询不会受益于任何形式的PARTITIONing
. 所以,我建议不要分区。
索引
鉴于几乎所有这些SELECTs
都是这两个:
select * from app_uses where ID='5labcvnaxvb11egw4w0or0wq4';
SELECT * FROM app_uses where OBJ_ID = '5ldfjkhgdfkjhg631exlwu9tkrsmv'
ORDER BY DATE_TIME DESC;
这些是最佳的:
PRIMARY KEY(ID) -- as you have
INDEX(OBJ_ID, DATE_TIME) -- replace key(obj_id) with this
第二种情况的建议更改避免了对结果进行排序的需要,因为可以简单地按所需顺序获取所需的行。(除非它有很多行,否则这不太可能产生太大的性能差异。UUID 问题主导了性能问题。)
推荐阅读
- python - 即使在 __init__ 之后,Python Import 也会给出未找到的包
- shopware - 如何将商店软件与其他应用程序集成?
- spring-cloud-stream - 路由器作为 Spring Cloud 数据流中的处理器
- node.js - 尝试安装 Angular Cli 或更新当前 Node.js 版本时出错
- swift - Scrollview 在键盘出现时创建插图,但不会自动向上移动到 textview
- android - Xamarin 在刷新顶部显示空单元格的列表后形成没有缓存策略的列表视图
- java - 如何在 Spring Security xml 中设置响应标头?
- docker - 如何将目录从容器挂载到Docker中的主机?
- javascript - React - 包装父组件和直接子组件
- swift - 'Publishers.Once' 的替代方案是什么?