MySQL 的动态分区

Posted

技术标签:

【中文标题】MySQL 的动态分区【英文标题】:Dynamic PARTITION for MySQL 【发布时间】:2022-01-22 21:16:12 【问题描述】:

我正在尝试为我的test 表按分区进行动态拆分,该表在启动此脚本之前有 2 个分区 p0 和 p1:

DROP PROCEDURE IF EXISTS part_splitting;

DELIMITER //
CREATE PROCEDURE part_splitting()
BEGIN

SELECT DATE_FORMAT(MIN(createdOn), "%Y-%m-%d") INTO @s FROM test;
SELECT DATE_FORMAT(MAX(createdOn), "%Y-%m-%d") + INTERVAL 1 MONTH INTO @e FROM test;
    SET @part_init := CONCAT( `ALTER TABLE test
    PARTITION BY RANGE COLUMNS(createdOn) (
     PARTITION p0 VALUES LESS THAN (`,@s,`)
    )`);
  PREPARE stmt1 FROM @part_init;
  EXECUTE stmt1;

  WHILE @s < @e DO
    SELECT CONCAT('p', DATE_FORMAT(@s,'%y/%m/%d')) INTO title

    SET @part_adding := CONCAT(`ALTER TABLE test ADD PARTITION (PARTITION `,title,` VALUES LESS THAN(`,DATE_FORMAT(@s,'%y/%m/%d'),`))`);
    PREPARE stmt2 FROM @part_adding;
    EXECUTE stmt2;
    
    SET @s = @s + INTERVAL 1 day;
  END WHILE;
END //
DELIMITER ;

CALL part_splitting(); 

目前,执行后出现此错误:

SQL Error [1054] [42S22]: Unknown column 'ALTER TABLE test
    PARTITION BY RANGE COLUMNS(createdOn) (
     PARTITION p0 VALUES LESS THAN (' in 'field list'
  Unknown column 'ALTER TABLE test
    PARTITION BY RANGE COLUMNS(createdOn) (
     PARTITION p0 VALUES LESS THAN (' in 'field list'
  Unknown column 'ALTER TABLE test
    PARTITION BY RANGE COLUMNS(createdOn) (
     PARTITION p0 VALUES LESS THAN (' in 'field list'

这里的关键思想是每周通过 SQL 事件动态创建分区,并从表中的最小天开始到最后一个日期加上提前 1 个月按天分割

这里有人有什么想法吗? 感谢您的帮助!

更新:

KUDOS@Bill Karwin 寻求答案 - 真的很酷!

所以我终于有了这个工作语法:

DROP PROCEDURE IF EXISTS part_splitting;

DELIMITER $$
CREATE PROCEDURE part_splitting()
BEGIN

SELECT DATE_FORMAT(MIN(createdOn), "%Y-%m-%d") INTO @s FROM test;
SELECT DATE_FORMAT(MAX(createdOn), "%Y-%m-%d") + INTERVAL 1 MONTH INTO @e FROM test;
    SET @part_init := CONCAT( 'ALTER TABLE test
    PARTITION BY RANGE COLUMNS(createdOn) (
     PARTITION p0 VALUES LESS THAN (',QUOTE(@s),'),
     PARTITION pMax VALUES LESS THAN(MAXVALUE)
    )');
  PREPARE stmt1 FROM @part_init;
  EXECUTE stmt1;

  WHILE @s < @e DO
    SET @s = @s + INTERVAL 1 day;
    SELECT CONCAT('p', DATE_FORMAT(@s,"%Y%m%d")) INTO @title;

    SET @part_adding := CONCAT('ALTER TABLE test ADD PARTITION ( PARTITION ',@title,' VALUES LESS THAN (',QUOTE(@s),') );');
    PREPARE stmt2 FROM @part_adding;
    EXECUTE stmt2;
  END WHILE;

END 
$$
DELIMITER ;

CALL part_splitting();

但是:

看起来这种定期运行和动态添加新分区的方法不起作用,因为我们遇到了错误: "Error Code: 1493. VALUES LESS THAN value must be strictly increasing for each partition 这来自之前创建某些分区时的情况。

目前,我不知道如何在循环中跳过这些步骤。看起来我们每次都可以使用...REORGANIZE PARTITION pMax...,但为此,我们需要知道pMax之前的最后日期条件,它是在上次运行时创建的。 (但我猜这是另一个故事......)

【问题讨论】:

我不确定分区是否是这样工作的。您不想每周更改分区。您将其设置为预先按周分区,分区数量等于您的服务器数量。 PARTITION BY HASH(CONCAT(YEAR(your_date), WEEK(your_date))) 当涉及到数据的分布时,看看 Ketama 算法,它提供了跨分布式密钥的一致性。也看看这个MongoDB sharding guide,因为它或多或少是等价的,以防你更熟悉MongoDB。 @DanielW。是的,这不是分区的常规情况。我们真的想每周(周末)每天重新分区,并根据表中的当前日期为该功能添加一些新分区。另一项工作会不时截断表格时间 - 因此无需保留空白章节,另一方面,我们需要一些新的章节。 【参考方案1】:

好的,错误的原因是您在应该使用单引号时使用了反引号。

SET @part_init := CONCAT( `ALTER TABLE...
                          ^

这会使您的整个 ALTER TABLE... 字符串被解释为 SQL 标识符,而不是字符串。所以解析器试图找到一个名称与该字符串匹配的表或列,当然它不能。

除了这个问题,你在开发这个问题时还会遇到其他问题。

CONCAT( 'ALTER TABLE test
PARTITION BY RANGE COLUMNS(createdOn) (
 PARTITION p0 VALUES LESS THAN (',@s,'))')
                               ^      ^

@s 的值是一个日期,对吧?但是您正在格式化的 SQL 不会在日期周围加上引号。所以它最终会是这样的:

...VALUES LESS THAN(2021-12-21)

这不是错误,但它不会按照您的想法进行。它将计算算术表达式 2021 减 12 减 21,返回 1988。

你需要用这样的引号来格式化日期:

...VALUES LESS THAN('2021-12-21')

一个简单的方法是这样的:

CONCAT( 'ALTER TABLE test
PARTITION BY RANGE COLUMNS(createdOn) (
 PARTITION p0 VALUES LESS THAN (',QUOTE(@s),'))')

还有其他问题。

除非您在执行此操作之前将表截断为零行,否则您的初始 ALTER TABLE 将不起作用,因为根据定义,单个分区仅存储小于最小值的值。因此,其他现有数据行没有分区。你试过了吗?

逐个添加分区将导致至少几十个 ALTER TABLE 语句,如果不是数百个。这样做需要很长时间,而且没有必要。您应该在一个 ALTER TABLE 语句中重新分区整个表。

您应该学习如何使用 DROP PARTITION 和 REORGANIZE PARTITION。这个留给你查手册吧。

最后,对像createdOn 这样的列进行分区,我会询问该列是否是表中每个主键或唯一键的一部分。这是分区列的要求。阅读https://dev.mysql.com/doc/refman/8.0/en/partitioning-limitations-partitioning-keys-unique-keys.html


回复:

我对此进行了测试:

DELIMITER $$
CREATE PROCEDURE part_splitting()
BEGIN

    SELECT DATE_FORMAT(MIN(createdOn), "%Y-%m-%d") INTO @s FROM test;
    SELECT DATE_FORMAT(MAX(createdOn), "%Y-%m-%d") + INTERVAL 1 MONTH INTO @e FROM test;
    SET @partition_sql = CONCAT( 'ALTER TABLE test
    PARTITION BY RANGE COLUMNS(createdOn) (
     PARTITION p0 VALUES LESS THAN (',QUOTE(@s),')');

    WHILE @s < @e DO
      SET @s = @s + INTERVAL 1 day;
      SET @partition_sql = CONCAT(@partition_sql, ',
     PARTITION p', DATE_FORMAT(@s,"%Y%m%d"), ' VALUES LESS THAN (',QUOTE(@s),')');
    END WHILE;

    SET @partition_sql = CONCAT(@partition_sql, ', 
     PARTITION pMax VALUES LESS THAN (MAXVALUE)
    );');

    SELECT @partition_sql;

END 
$$
DELIMITER ;

输出:

ALTER TABLE test
    PARTITION BY RANGE COLUMNS(createdOn) (
     PARTITION p0 VALUES LESS THAN ('2021-12-21'), 
     PARTITION p20211222 VALUES LESS THAN ('2021-12-22'), 
     PARTITION p20211223 VALUES LESS THAN ('2021-12-23'), 
     ...one for each day...
     PARTITION p20220121 VALUES LESS THAN ('2022-01-21'),
     PARTITION pMax VALUES LESS THAN (MAXVALUE)
    );

我只输出了 ALTER TABLE 语句,但您可以改为 PREPARE & EXECUTE。关键是循环附加了更多的分区子句,因此整个 ALTER TABLE 一步完成,而不是在每次循环迭代中每个分区一个 ALTER TABLE。

【讨论】:

你发帖,我学到东西,不是很好吗?期待购买您的书? 非常感谢@Bill Karwin!!!是的 createdOn 是主键的一部分。关于这部分...So there's no partition for the other existing rows with data. ... 表在此更改之前有一些分区。 (之前尝试手动执行),是的,第一个版本在开始时有两个分区,但对于这个例子,我删除了第二个分区,因为我认为这可能很容易调试 关于这部分:... Adding partitions one by one is going to result in at least dozens of ALTER TABLE statements, if not hundreds. It'll take too long to do that, and it's not necessary. You should repartition the whole table in one ALTER TABLE statement.... 还是不知道在 WHILE DO 循环中是怎么做的……你有例子吗? 您的声誉得分为 3000+,但您不知道如何在循环中附加 ", PARTITION pN VALUES LESS THAN (...)" 之类的字符串? :-) @Bill Karwin,请原谅错误的措辞。问题主要是关于我如何组织这部分...the whole table in one ALTER TABLE statement 像这样:ALTER TABLE test PARTITION BY RANGE COLUMNS(createdOn) ( PARTITION p0 VALUES LESS THAN (@s), WHILE @s &lt; @e DO SET @s = @s + INTERVAL 1 day; PARTITION pN VALUES LESS THAN (QUOTE(@s)) END PARTITION pMax VALUES LESS THAN (MAXVALUE) ); 然后运行一次。但据我所知,我们不能拆分 PARTITION 表达式并在中间注入一些 WHILE 或其他循环块。

以上是关于MySQL 的动态分区的主要内容,如果未能解决你的问题,请参考以下文章

动态分区和静态分区的区别

Hive表的动态分区和静态分区

Apache Spark 动态分区 OverWrite 问题

Hive动态分区

Hive分区参考

Hive 动态分区