背景

目前系统中有部分日志表,每天增量接近百万条,这导致了几个问题:

  • 占用过多主库的存储
  • 影响整体查询效率

由于该部分数据是日志数据,核心我们只要保留近几天的日志即可,其他日志数据需要做归档备份。所以我们需要将近几天之前的数据导出到备份表,然后上传归档存储,然后删除该表中的已备份数据。

P.S:  这里说明下,确实直接按天分表,然后处理按照表进行备份和 drop更合理。但是由于系统的种种限制,这里选择了上述流程。

在使用上述方式时,遇到了一个核心问题,就是删除历史数据时,导致产生了不可忍受的慢SQL,嫉妒影响性能。

基于上面这个场景,才考虑引入 MySQL Partition,按照时间创建分区,然后根据分区进行数据删除的场景,解决性能问题。

先说直接删除和引入 Partition后删除的性能对比结果:

  • 在本地测试删除800w条数据时耗时140s+(按照ID 范围删除)
  • 数据按天进行分区,直接 drop指定partition,本地测试为0.002s左右

下面,开始聊聊本次引入partition遇到的一些坑吧!

各种坑

一、确认你是用的MySQL支持分区

SHOW PLUGINS
-- 查看 partition 的 status = ACTIVE
Untitled

二、由于需要按照日期创建分期,使用字段 created_at 按照指定日期创建分区:

创建分区命令:

-- 添加分区
ALTER TABLE partition_test
PARTITION BY RANGE(TO_DAYS(created_at)) (
    PARTITION p20230809 VALUES LESS THAN (TO_DAYS('2023-08-10'))
);

遇到如下报错:

1486 – Constant, random or timezone-dependent expressions in (sub)partitioning function are not allowed, Time: 0.042000s

原因:

created_at 是一个时间戳列,而 TO_DAYS() 函数不起作用,需要使用 UNIX_TIMESTAMP() 函数来获得时间戳的秒数,然后以秒数为基准进行分区。

以下是一个示例代码,展示如何使用 UNIX_TIMESTAMP() 函数进行分区:

-- 添加分区
ALTER TABLE partition_test
PARTITION BY RANGE(UNIX_TIMESTAMP(created_at)) (
    PARTITION p20230101 VALUES LESS THAN (UNIX_TIMESTAMP('2023-01-02')),
    PARTITION p20230102 VALUES LESS THAN (UNIX_TIMESTAMP('2023-01-03'))
);

三、使用UNIX_TIMESTAMP,仍然报错,具体如下:

1171 – All parts of a PRIMARY KEY must be NOT NULL; if you need NULL in a key, use UNIQUE instead, Time: 0.003000s

原因:

created_at作为分区列,需要是非空。

解决方案: 调整created_at为 Not null,同时设置default值

ALTER TABLE partition_test_new
MODIFY COLUMN `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP AFTER `sex`;

四、继续执行,还是报错:

1503 – A PRIMARY KEY must include all columns in the table’s partitioning function, Time: 0.025000s

原因:

这个错误表示,在使用分区函数进行分区时,主键必须包含所有参与分区的列。

解决方案:

需要先添加 created_at 为联合主键。

ALTER TABLE `partition_test` 
DROP PRIMARY KEY,
ADD PRIMARY KEY (`id`, `created_at`) USING BTREE;

五、完成上述调整后,继续执行,仍出现报错:

1526 – Table has no partition for value 1691597196, Time: 0.045000s

原因:

对应的数据无法找到分区,需要创建对应分区。由于创建分区时,包括了如下分区:


PARTITION p20230101 VALUES LESS THAN (UNIX_TIMESTAMP('2023-01-02')),
PARTITION p20230102 VALUES LESS THAN (UNIX_TIMESTAMP('2023-01-03'))

实际数据中, 存在 created_at > 2023-01-03 的数据,导致该部分数据无法找到分区。因此需要创建包括所有数据的分区

六、想要新增一个partition

新增命令如下:

ALTER TABLE partition_test
ADD PARTITION (
    PARTITION p20220101 VALUES LESS THAN (UNIX_TIMESTAMP('2022-01-02'))
);

报错:

1493 – VALUES LESS THAN value must be strictly increasing for each partition, Time: 0.003000s

原因:

在重新组织分区时,新分区的划分值必须严格大于旧分区的划分值。

需要确保在创建新分区时,新分区的划分值(VALUES LESS THAN)是严格大于原始分区划分值的。例如,如果原始分区的划分值是 ‘2023-01-03’,那么新分区的划分值应该是更大的日期,比如 ‘2023-01-04’。这是保证分区按顺序排列的关键。

七、对于没有创建分区的表,需要创建分区时

不能使用命令:

ALTER TABLE table_name
ADD PARTITION (
    PARTITION partition_name VALUES LESS THAN (UNIX_TIMESTAMP('YYYY-MM-DD'))
);

会报错如下:

1505 – Partition management on a not partitioned table is not possible, Time: 0.001000s

此时要求至少存在一个分区,否则使用alter table add partition 添加分区的时候就会报错。

需要使用命令:

-- 添加分区
ALTER TABLE partition_test
PARTITION BY RANGE(UNIX_TIMESTAMP(created_at)) (
    PARTITION p20230809 VALUES LESS THAN (UNIX_TIMESTAMP('2023-08-10'))
);

官方建议

最好的是在创建表时就规划好分区,原因如下:

在创建表时定义分区的好处包括:

  1. 提前规划: 你可以在设计表结构时就考虑到数据的分布和访问模式,从而更好地规划分区策略。
  2. 更高效的插入: 表在插入数据之前已经按照分区规则划分好,插入数据时可以直接插入到相应的分区,提高插入性能。
  3. 更容易维护: 表在创建时就设置了分区,将来你可以更方便地进行数据维护、备份和恢复操作。

为了最大程度地提高性能和避免后续操作的复杂性,建议在创建表时就定义好分区策略。这样可以更好地规划和管理数据,并确保在插入数据时就充分利用分区的性能优势。

最后修改日期: 2023年9月16日

留言

撰写回覆或留言

发布留言必须填写的电子邮件地址不会公开。