在 MySQL Table 的基于范围的分区中确定分区键

Determining partitioning key in range based partitioning of a MySQL Table

我在 MySQL 中研究了一段时间有关数据库分区的问题。由于我的数据库中有一个不断增长的 table,我想到使用分区作为优化它的有效工具。我只对保留最近的数据(比如最近 6 个月)感兴趣并且 table 有一个列名 'CREATED_AT' (TIMESTAMP,NON-PRIMARY),我脑海中浮现的方法如下

但是,只有将'CREATED_AT'字段作为主字段才能实现分区。但是这不违反主键原则吗?因为同一个字段是非唯一的并且可以有大量具有相同值的行,所以将其标记为主要字段不是反模式吗?在这种情况下是否有任何解决方法来实现基于时间的范围分区?

这是一个阻止许多 MySQL 用户使用分区的问题。

您用于分区键的列必须在 table 的每个 PRIMARY KEY 或 UNIQUE KEY 中。它不一定是那些键中的 only 列(因为键可以是多列),但它必须是 part of每个唯一键。

仍然,在许多 table 中,它会违反 table 的逻辑设计。所以分区不实用。

你可以咬紧牙关设计一个 table 带有折衷设计的分区:

create table mytable (
 id bigint auto_increment not null,
 created_at datetime not null,
 primary key (id, created_at)
) partition by range columns (created_at) (
 partition p20190101 values less than ('2019-01-01'),
 partition p20190201 values less than ('2019-02-01'),
 partition p20190301 values less than ('2019-03-01'),
 partition p20190401 values less than ('2019-04-01'),
 -- etc...
 partition pMAX values less than (MAXVALUE)
);

我测试了这个table,我定义的时候没有错误。尽管此 table 在技术上允许具有相同 id 值的多个行(如果它们具有不同的时间戳),但实际上您可以对应用程序进行编码以仅让 id 值自动递增,而永远不会更改 id。只要您的代码是唯一插入数据的应用程序,您或多或少可以保证数据不包含具有相同 id.

的多行

您可能认为可以添加辅助唯一键约束来强制 id 本身必须是唯一的。但这违反了分区规则:

mysql> alter table mytable add unique key (id);
ERROR 1503 (HY000): A UNIQUE INDEX must include all columns in the table's partitioning function

您只需相信您的应用程序不会插入无效数据。

或者忘记使用分区,而只是将索引添加到 created_at 列,并使用增量 DELETE 而不是使用 DROP PARTITION 来修剪旧数据。

我看到几乎在所有情况下都使用后一种策略。通常,让 RDBMS 在 id 列上强制执行严格的唯一性很重要。不强制执行此唯一性是不安全的。


回复您的评论:

Isn't dropping of an entire partition a much cheaper operartion than performing incremental deletes?

是也不是。

DELETE可以回滚,所以会产生一些开销,比如把数据临时存放在回滚段。另一方面,它只锁定与索引搜索匹配的行。

删除分区不会回滚,因此可以跳过一些步骤。但是它做了一个 ALTER TABLE,所以它需要先获取整个 table 上的元数据锁。任何并发查询,无论是读还是写,都会阻塞它并被它阻塞。

演示:

打开两个MySQL客户端windows。在第一个会话中执行此操作:

mysql> START TRANSACTION;
mysql> SELECT * FROM mytable;

这在 table 上持有一个元数据锁,它阻止像 ALTER TABLE.

这样的事情

第二个window:

mysql> ALTER TABLE mytable DROP PARTITION p20190101;
<pauses, waiting for the metadata lock held by the first session!>

您甚至可以打开第三个会话并执行此操作:

mysql> SELECT * FROM mytable;
<also pauses>

第二个 SELECT 在 ALTER TABLE 后面等待。他们都在排队等待元数据锁定。

如果我提交第一个 SELECT,那么 ALTER TABLE 最终完成:

mysql> ALTER TABLE mytable DROP PARTITION p20190101;
Query OK, 0 rows affected (6 min 25.25 sec)

这 6 分 25 秒并不是因为执行 DROP PARTITION 需要很长时间。这是因为我在写这篇 post.

时没有提交我的事务那么久

元数据锁等待不会像 InnoDB 行锁那样超时,它会在 50 秒后超时。默认元数据锁定超时为 1 年! 请参阅 https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html#sysvar_lock_wait_timeout

像 ALTER TABLE、DROP TABLE、RENAME TABLE 这样的语句,甚至像 CREATE TRIGGER 这样的语句都需要获取元数据锁。

因此,在某些情况下,根据您是否有长期 运行 事务持有元数据锁,使用 DELETE 增量删除数据可能对您的并发吞吐量更好,即使这需要更长的时间。