在 MySQL Table 的基于范围的分区中确定分区键
Determining partitioning key in range based partitioning of a MySQL Table
我在 MySQL 中研究了一段时间有关数据库分区的问题。由于我的数据库中有一个不断增长的 table,我想到使用分区作为优化它的有效工具。我只对保留最近的数据(比如最近 6 个月)感兴趣并且 table 有一个列名 'CREATED_AT' (TIMESTAMP,NON-PRIMARY),我脑海中浮现的方法如下
- 使用 'CREATED_AT' 作为分区键在 table 上创建基于时间的范围分区。
- 运行 数据库级事件定期发生并删除过时的分区。 (超过 6 个月)。
但是,只有将'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 增量删除数据可能对您的并发吞吐量更好,即使这需要更长的时间。
我在 MySQL 中研究了一段时间有关数据库分区的问题。由于我的数据库中有一个不断增长的 table,我想到使用分区作为优化它的有效工具。我只对保留最近的数据(比如最近 6 个月)感兴趣并且 table 有一个列名 'CREATED_AT' (TIMESTAMP,NON-PRIMARY),我脑海中浮现的方法如下
- 使用 'CREATED_AT' 作为分区键在 table 上创建基于时间的范围分区。
- 运行 数据库级事件定期发生并删除过时的分区。 (超过 6 个月)。
但是,只有将'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 增量删除数据可能对您的并发吞吐量更好,即使这需要更长的时间。