mysql - 在 MySQL 表的基于范围的分区中确定分区键
问题描述
我一直在研究 MySQL 中的数据库分区。由于我的数据库中有一个不断增长的表,因此我考虑使用分区作为优化它的有效工具。我只对保留最近的数据(比如最近 6 个月)感兴趣,并且该表有一个列名“CREATED_AT”(TIMESTAMP,NON-PRIMARY),我想到的方法如下
- 使用“CREATED_AT”作为分区键在表上创建基于时间的范围分区。
- 定期运行数据库级别事件并删除过时的分区。(超过 6 个月)。
但是,只有当我将 'CREATED_AT' 字段作为主要字段时,才能实现分区。但这不违反主键原则吗?由于同一个字段是非唯一的,并且可以有大量具有相同值的行,因此将其标记为主要字段不是反模式吗?在这种情况下,是否有任何解决方法可以实现基于时间的范围分区?
解决方案
这是一个阻止许多 MySQL 用户使用分区的问题。
您用于分区键的列必须在表的每个 PRIMARY KEY 或 UNIQUE KEY 中。它不必是这些键中的唯一列(因为键可以是多列),但它必须是每个唯一键的一部分。
不过,在许多表中,它会违反表的逻辑设计。所以分区是不切实际的。
您可以咬紧牙关,设计一张带有折衷设计的隔板的桌子:
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)
);
我测试了这个表,定义它时没有错误。即使此表在技术上允许多个具有相同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
列强制执行严格的唯一性很重要。不强制执行这种唯一性是不安全的。
回复您的评论:
删除整个分区不是比执行增量删除便宜得多吗?
是和不是。
DELETE 可以回滚,因此会产生一些开销,比如将数据临时存储在回滚段中。另一方面,它只锁定与索引搜索匹配的行。
删除分区不会回滚,因此可以跳过一些步骤。但是它做了一个 ALTER TABLE,所以它需要首先获取整个表的元数据锁。任何并发查询,无论是读还是写,都会阻塞它并被它阻塞。
演示:
打开两个 MySQL 客户端窗口。在第一个会话中这样做:
mysql> START TRANSACTION;
mysql> SELECT * FROM mytable;
这在表上持有一个元数据锁,它会阻止像 ALTER TABLE 这样的东西。
在第二个窗口中:
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 需要很长时间。这是因为我在写这篇文章的时候很久没有提交我的交易。
元数据锁等待不会像 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 增量删除数据可能会更好地提高并发吞吐量,即使需要更长的时间。
推荐阅读
- javascript - 从 YYYY-DD-MM HH:MM:SS UTC--3 格式在 JavaScript/jQuery 中使用时区解析日期和时间
- javascript - 预期的 catch() 或返回(promise/catch-or-return)
- angular - Ionic 3 + Angular 5:发布数据示例
- php - 我只想删除邮件标题中写的 via 和额外的东西
- javascript - 如何使用javascript检测iphone/ipad中屏幕键盘的高度
- java - Java中的分页sql表
- c++ - 尽管明确声明为私有,但 c++ 函数仍可公开调用
- c# - C# 对象连接与动态对象中的动态标签
- sql - 在同一张表上动态交叉选择查询
- html - 在顶部显示一个 jssor 滑块?