通过分区提高 RDS 的 MySQL 性能

Improving MySQL performance on RDS by partitioning

我正在尝试提高 RDS 上 MySQL 8.0.20 数据库中一些大型表(可能是数百万条记录)的性能。

扩展数据库实例和 IOPS 不是可行的方法,因为它非常昂贵(数据库全天候 24/7 运行)。 适当的索引(包括复合索引)已经存在以提高查询性能。 数据库主要是读取密集型,偶尔会有大量写入 - 当这些写入发生时,读取可能同时也同样庞大。

我考虑过分区。由于 MySQL 不支持垂直分区,我考虑过进行水平分区——这对于这些大表应该非常有效,因为它们包含来自 dozens/hundreds 个帐户的 activity 记录,并存储每个帐户在单独分区中的记录对我来说很有意义。 但是这些表确实包含一些外键约束,这排除了使用 MySQL 的水平分区:Restrictions and Limitations on Partitioning

Foreign keys not supported for partitioned InnoDB tables. Partitioned tables using the InnoDB storage engine do not support foreign keys. More specifically, this means that the following two statements are true:

  1. No definition of an InnoDB table employing user-defined partitioning may contain foreign key references; no InnoDB table whose definition contains foreign key references may be partitioned.

  2. No InnoDB table definition may contain a foreign key reference to a user-partitioned table; no InnoDB table with user-defined partitioning may contain columns referenced by foreign keys.

除了通过使用单独的表在每个帐户的基础上存储 activity 记录来进行“分片”之外,我还有什么选择?这将需要对代码进行重大更改以适应此类表格。希望有更好的方法,只需要更改 MySQL,而不是应用程序代码。如果需要更改代码 - 越少越好:)

在分片或分区之前,首先分析您的查询以确保它们已尽可能优化。这通常意味着专门设计索引来支持您 运行 的查询。您可能会喜欢我的介绍 How to Design Indexes, Really (video).

分区并不像人们想象的那样是一种解决方案。它有很多限制,包括您发现的外键问题。除此之外,它只改进了可以利用分区 p运行ing.

的查询

另外,我已经为我目前的工作和以前的工作做了很多 Amazon RDS 的基准测试。 RDS 很慢。真的很慢。它使用远程 EBS 存储,因此每次从存储读取或写入存储都必然会产生开销。 RDS 只是不适合任何需要高性能的应用程序。

Amazon Aurora 在延迟和吞吐量方面明显更好。但它也非常昂贵。您使用它的次数越多,您使用 I/O 请求的次数就越多,他们会为此额外收费。对于繁忙的应用程序,您最终花费的费用与为具有高配置 IOPS 的 RDS 花费的费用一样多。

我发现在云中获得高性能的唯一方法是忘记像 RDS 和 Aurora 这样的托管数据库,而是在 ec2 实例上安装 运行 你自己的 MySQL 实例使用本地连接的 NVMe 存储。这意味着 ec2 实例的 i3 系列。但本地存储是临时实例存储,因此如果实例重新启动,您将丢失数据。所以你必须添加一个或多个副本,并有一个故障转移计划。

如果您需要云端的 OLTP 数据库,并且还需要顶级性能,您要么必须花费 $$$ 购买托管数据库,要么需要聘请全职 DevOps 和 DBA 人员到运行它。

很抱歉告诉你这个坏消息,但 TANSTAAFL adage 仍然是正确的。

storing each account's records in a separate partition makes a lot of sense to me

相反,PRIMARY KEY 开始 acct_id。这提供的性能至少与 PARTITION BY acct_id 一样好,节省磁盘空间 space,并将一个帐户的数据“集群”在一起以用于“参考位置”。

The DB is mostly read-heavy

副本允许 'infinite' 缩放读取。但是如果你现在不是单机超载,可能就没有这个必要了。

with occasional massive writes

让我们讨论有助于解决此问题的技术。请解释这些写入的内容——hourly/daily/sporadic?替换随机行/整个 table / 等等?关闭什么?等等

Proper indexes (including composite ones) do already exist to improve the query performance.

使用慢日志(long_query_time = 1 或更低)进行验证。使用 pt-query-digest 查找最热门的一两个查询。将它们展示给我们——我们可以帮助您“跳出框框思考”。

read-heavy

工作集大小是否小于innodb_buffer_pool_size?也就是说,你是 CPU 绑定的而不是 I/O-bound?

有关分区的更多信息

PRIMARY KEY(acct_id, ..some other columns..) 主要根据 acct_id 对数据进行排序并使其高效:WHERE acct_id=123 AND ....

PARTITION BY .. (acct_id) -- PARTITION 作为单独的“table”实现。 “分区修剪”是决定查询需要哪些分区的行为。所以 WHERE acct_id=123 AND ... 将首先进行修剪,然后在“table”中查找行来处理 AND ...。希望有一个好的索引(可能是 PRIMARY KEY)来处理这部分过滤。

修剪有点取代了一层 BTree。很难预测哪个会更慢或更快。

请注意,当按 acct_id 进行分区时,启动 该列的索引通常效率不高。 (不过PK的时候需要后期)

大删

有几种方法可以在最大程度地减少对系统的影响的同时进行“大删除”。按日期分区是最佳的,但听起来对您的数据类型不可行。查看此处列出的其他人:http://mysql.rjweb.org/doc.php/deletebig

既然你说的删除率通常小于15%,那么“复制需要保留”的技巧也不适用