13

我有一张看起来像这样的桌子:

CREATE TABLE `Calls` (
  `calendar_id` int(11) NOT NULL,
  `db_date` timestamp NOT NULL,
  `cgn` varchar(32) DEFAULT NULL,
  `cpn` varchar(32) DEFAULT NULL,
  PRIMARY KEY (`calendar_id`),
  KEY `db_date_idx` (`db_date`)
) 
 PARTITION BY RANGE (calendar_id)(
   PARTITION p20091024 VALUES LESS THAN (20091024) ,
   PARTITION p20091025 VALUES LESS THAN (20091025));

我可以以某种方式使用 mysql 调度程序自动添加一个新分区(提前 2 天) - 我正在寻找一个示例,每天都会添加一个新分区 - 它会运行类似

alter table Calls add partition (partition p20091026 values less than(20091026));

其中 p20091026/20091026 是在计划任务运行时构造的,从现在 + 2 天派生值。(或者我最好通过 cron 编写脚本?)

4

2 回答 2

32

是的,你可以这样做。

请注意,调度程序默认情况下是不活动的(请参阅事件调度程序配置),因此它不是零风险选项。例如,如果您的运营团队将您的应用程序迁移到新服务器,但忘记启用调度程序,您的应用程序将被淹没。还需要特殊权限,这可能需要在新服务器上再次设置。

我的建议:首先,创建一个处理定期分区维护的存储过程(参见下面的代码示例):如果表变得太大,则删除旧分区,并添加足够的新分区(例如 1 周),这样即使维护过程不是暂时不运行,您的应用程序不会死机。

然后冗余地安排对该存储过程的调用。使用 MySQL 调度程序,使用 cron 作业,并使用您喜欢的任何其他方式。然后,如果一个调度程序不工作,另一个调度程序可以填补空缺。如果您正确设计了存储过程,那么在不需要执行任何操作的情况下执行空操作应该很便宜。您甚至可能希望从您的应用程序中调用它,例如作为生成长期运行报告时的第一条语句,或者作为您日常 ETL 流程的一部分(如果您有的话)。我的观点是,计划任务的致命弱点是确保调度程序实际工作——所以在这里考虑冗余。

只要确保不要同时安排所有通话,这样他们就不会互相踩踏!:-)

这是您的维护过程可能看起来的代码示例——首先它修剪旧分区,然后添加新分区。我留下了错误检查和防止多个同时执行作为读者的练习。

DELIMITER $$

DROP PROCEDURE IF EXISTS `test`.`UpdatePartitions` $$
CREATE PROCEDURE `test`.`UpdatePartitions` ()
BEGIN

  DECLARE maxpart_date date;
  DECLARE partition_count int;
  DECLARE minpart date;
  DECLARE droppart_sql date;
  DECLARE newpart_date date;
  DECLARE newpart_sql varchar(500);

  SELECT COUNT(*)
    INTO partition_count
    FROM INFORMATION_SCHEMA.PARTITIONS
    WHERE TABLE_NAME='Calls' AND TABLE_SCHEMA='test';

  -- first, deal with pruning old partitions
  -- TODO: set your desired # of partitions below, or make it parameterizable
  WHILE (partition_count > 1000)
  DO

    -- optionally, do something here to deal with the parition you're dropping, e.g.
    -- copy the data into an archive table

     SELECT MIN(PARTITION_DESCRIPTION)
       INTO minpart
       FROM INFORMATION_SCHEMA.PARTITIONS
       WHERE TABLE_NAME='Calls' AND TABLE_SCHEMA='test';

     SET @sql := CONCAT('ALTER TABLE Calls DROP PARTITION p'
                        , CAST((minpart+0) as char(8))
                        , ';');

     PREPARE stmt FROM @sql;
     EXECUTE stmt;
     DEALLOCATE PREPARE stmt;

    SELECT COUNT(*)
      INTO partition_count
      FROM INFORMATION_SCHEMA.PARTITIONS
      WHERE TABLE_NAME='Calls' AND TABLE_SCHEMA='test';


  END WHILE;

  SELECT MAX(PARTITION_DESCRIPTION)
    INTO maxpart_date
    FROM INFORMATION_SCHEMA.PARTITIONS
    WHERE TABLE_NAME='Calls' AND TABLE_SCHEMA='test';

  -- create enough partitions for at least the next week
  WHILE (maxpart_date < CURDATE() + INTERVAL 7 DAY)
  DO

    SET newpart_date := maxpart_date + INTERVAL 1 DAY;
    SET @sql := CONCAT('ALTER TABLE Calls ADD PARTITION (PARTITION p'
                        , CAST((newpart_date+0) as char(8))
                        , ' values less than('
                        , CAST((newpart_date+0) as char(8))
                        , '));');

    PREPARE stmt FROM @sql;
    EXECUTE stmt;
    DEALLOCATE PREPARE stmt;

    SELECT MAX(PARTITION_DESCRIPTION)
      INTO maxpart_date
      FROM INFORMATION_SCHEMA.PARTITIONS
      WHERE TABLE_NAME='Calls' AND TABLE_SCHEMA='test';

  END WHILE;

END $$

DELIMITER ;

顺便说一句,分区维护(确保提前创建新分区,修剪旧分区等)恕我直言,对于自动化至关重要。我个人见过一个大型企业数据仓库宕机一天,因为最初创建了一年的分区,但没有人记得在明年到来时创建更多分区。所以你在这里考虑自动化是非常好的——这对你正在从事的项目来说是个好兆头。:-)

于 2009-11-28T14:09:51.987 回答
9

贾斯汀那里的出色解决方案。我将他的代码作为我当前项目的起点,并想提一下我在实施它时遇到的一些事情。

  1. 您在其上运行的表中的现有分区结构不应包含 MAXVALUE 类型的分区 - 所有分区必须由文字日期分隔。这是因为 SELECT MAX(PARTITION_DESCRIPTION) 将返回 'MAXVALUE' 在下一步中无法转换为日期。如果在调用过程时收到奇怪的消息,例如:“<”的排序规则的非法混合,这可能是问题所在。

  2. 从 INFORMATION_SCHEMA 表中选择分区名称时,最好添加:“AND TABLE_SCHEMA = 'dbname'”,因为虽然同一个表(在不同的数据库中)可以存在多个具有相同名称的分区,但它们都被列出在 INFORMATION_SCHEMA 表中。如果没有 TABLE_SCHEMA 规范,您可以选择例如。MAX(PARTITION_DESCRIPTION) 将为您提供每个数据库中具有该名称的表的每个现有分区中的最大分区名称。

  3. 在某个地方,我遇到了 ALTER TABLE xxx ADD PARTITION 的问题,因为它在 Justin 的解决方案中,我认为分区名称(yyyymmdd)的相同格式被用作预期 yyyy-mm-dd 的分区分隔符(v5.6.2)。

  4. 默认行为是仅在将来根据需要添加分区。如果您想为过去创建分区,您需要首先为比您想要的最旧分区更早的日期设置一个分区。例如。如果您要保留过去 30 天的数据,请先添加一个分区,例如 35 天前,然后运行该过程。诚然,这可能只在一张空桌子上才可行,但我认为值得一提。

  5. 为了创建所需的过去/未来分区跨度,如 4. 所示,您最初需要运行该过程两次。对于上面 4. 中的示例,第一次运行将创建 -35 天的分区,以及必要的未来分区。然后第二次运行将修剪 -35 和 -30 之间的分区。

这是我目前正在使用的。从调用者的角度来看,我添加了一些参数以使其更加灵活。您可以指定数据库、表、当前日期以及为过去和将来保留多少分区。

我还更改了分区的命名,以便名为 p20110527 的分区代表从 2011 年 5 月 27 日 00:00开始的那一天,而不是那个时候结束的那一天。

仍然没有错误检查或防止同时执行:-)

DELIMITER $$

DROP PROCEDURE IF EXISTS UpdatePartitions $$

-- Procedure to delete old partitions and create new ones based on a given date.
-- partitions older than (today_date - days_past) will be dropped
-- enough new partitions will be made to cover until (today_date + days_future)
CREATE PROCEDURE UpdatePartitions (dbname TEXT, tblname TEXT, today_date DATE, days_past INT, days_future INT)
BEGIN

DECLARE maxpart_date date;
DECLARE partition_count int;
DECLARE minpart date;
DECLARE droppart_sql date;
DECLARE newpart_date date;
DECLARE newpart_sql varchar(500); 

SELECT COUNT(*)
INTO partition_count
FROM INFORMATION_SCHEMA.PARTITIONS
WHERE TABLE_NAME=tblname
AND TABLE_SCHEMA=dbname;

-- SELECT partition_count;

-- first, deal with pruning old partitions
WHILE (partition_count > days_past + days_future)
DO
-- optionally, do something here to deal with the parition you're dropping, e.g.
-- copy the data into an archive table

 SELECT STR_TO_DATE(MIN(PARTITION_DESCRIPTION), '''%Y-%m-%d''')
   INTO minpart
   FROM INFORMATION_SCHEMA.PARTITIONS
   WHERE TABLE_NAME=tblname
   AND TABLE_SCHEMA=dbname;

-- SELECT minpart;

 SET @sql := CONCAT('ALTER TABLE '
                    , tblname
                    , ' DROP PARTITION p'
                    , CAST(((minpart - INTERVAL 1 DAY)+0) as char(8))
                    , ';');

 -- SELECT @sql;
 PREPARE stmt FROM @sql;
 EXECUTE stmt;
 DEALLOCATE PREPARE stmt;

SELECT COUNT(*)
  INTO partition_count
  FROM INFORMATION_SCHEMA.PARTITIONS
  WHERE TABLE_NAME=tblname
  AND TABLE_SCHEMA=dbname;

-- SELECT partition_count;

END WHILE;

SELECT STR_TO_DATE(MAX(PARTITION_DESCRIPTION), '''%Y-%m-%d''')
INTO maxpart_date
FROM INFORMATION_SCHEMA.PARTITIONS
WHERE TABLE_NAME=tblname
AND TABLE_SCHEMA=dbname;

-- select maxpart_date;
-- create enough partitions for at least the next days_future days
WHILE (maxpart_date < today_date + INTERVAL days_future DAY)
DO

-- select 'here1';
SET newpart_date := maxpart_date + INTERVAL 1 DAY;
SET @sql := CONCAT('ALTER TABLE '
                    , tblname
                    , ' ADD PARTITION (PARTITION p'
                    , CAST(((newpart_date - INTERVAL 1 DAY)+0) as char(8))
                    , ' VALUES LESS THAN ('''
                    , newpart_date
                    , '''));');

-- SELECT @sql;
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

SELECT STR_TO_DATE(MAX(PARTITION_DESCRIPTION), '''%Y-%m-%d''')
  INTO maxpart_date
  FROM INFORMATION_SCHEMA.PARTITIONS
  WHERE TABLE_NAME=tblname
  AND TABLE_SCHEMA=dbname;

SET maxpart_date := newpart_date;

END WHILE;

END $$

DELIMITER ;
于 2011-05-27T03:31:35.120 回答