mysql 分区实践之按月份分区,定时增加分区和删除分区

按月份分区,这样再使用分区字段时间来查询数据将会很快,因为这样只需要扫描指定的分区。

1、创建表,并使用RANGE COLUMNS分区。按创建时间create_time字段分区;分区名使用p0、p1、p2、p3 .. 的形式;create_time字段小于2019-01-01的数据将进入p0 分区,依次类推。。

CREATE TABLE "box_fenqu" (
  "id" bigint(36) NOT NULL AUTO_INCREMENT COMMENT '主键',
  "create_by" varchar(50) DEFAULT NULL COMMENT '创建人',
  "create_time" datetime NOT NULL COMMENT '创建日期',
  "update_by" varchar(50) DEFAULT NULL COMMENT '更新人',
  "update_time" datetime DEFAULT NULL COMMENT '更新日期',
  "sys_org_code" varchar(64) DEFAULT NULL COMMENT '所属部门',
  "status" int(10) DEFAULT '0' COMMENT '状态',
  "number" varchar(32) DEFAULT NULL COMMENT '编号',
  "zi_number" varchar(32) DEFAULT NULL COMMENT '自编号',
  "house_address" varchar(32) DEFAULT NULL COMMENT '仓库地址',
  "sb_number" varchar(32) DEFAULT NULL COMMENT '设备id',
  "point_id" varchar(32) DEFAULT NULL COMMENT '投放点id',
  "point" varchar(32) DEFAULT NULL COMMENT '投放点',
  "confirm" int(32) DEFAULT '0' COMMENT '商户/企业用户确认入库,默认为0(未确认)1是已确认',
  "last_point" varchar(32) DEFAULT NULL COMMENT '最近一次投放点名',
  PRIMARY KEY ("id","create_time") USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=2120001 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC
 PARTITION BY RANGE  COLUMNS(create_time)
(PARTITION p0 VALUES LESS THAN ('2019-01-01') ENGINE = InnoDB,
 PARTITION p1 VALUES LESS THAN ('2019-02-01') ENGINE = InnoDB,
 PARTITION p2 VALUES LESS THAN ('2019-03-01') ENGINE = InnoDB,
 PARTITION p3 VALUES LESS THAN ('2019-04-01') ENGINE = InnoDB,
 PARTITION p4 VALUES LESS THAN ('2019-05-01') ENGINE = InnoDB,
 PARTITION p5 VALUES LESS THAN ('2019-06-01') ENGINE = InnoDB,
 PARTITION p6 VALUES LESS THAN ('2019-07-01') ENGINE = InnoDB,
 PARTITION p7 VALUES LESS THAN ('2019-08-01') ENGINE = InnoDB,
 PARTITION p8 VALUES LESS THAN ('2019-09-01') ENGINE = InnoDB,
 PARTITION p9 VALUES LESS THAN ('2019-10-01') ENGINE = InnoDB,
 PARTITION p10 VALUES LESS THAN ('2019-11-01') ENGINE = InnoDB,
 PARTITION p11 VALUES LESS THAN ('2019-12-01') ENGINE = InnoDB,
 PARTITION p12 VALUES LESS THAN ('2020-01-01') ENGINE = InnoDB,
 PARTITION p13 VALUES LESS THAN ('2020-02-01') ENGINE = InnoDB,
 PARTITION p14 VALUES LESS THAN ('2020-03-01') ENGINE = InnoDB,
 PARTITION p15 VALUES LESS THAN ('2020-04-01') ENGINE = InnoDB,
 PARTITION p16 VALUES LESS THAN ('2020-05-01') ENGINE = InnoDB,
 PARTITION p17 VALUES LESS THAN (MAXVALUE) ENGINE = InnoDB) 

2、创建存储过程proc_create_partition,用它来直接对box_fenqu表进行分区。其实就是将分区名加1,如p2加1就是p3;指定的日期范围就是往后面进一个月,保证最新的分区总是MAXVALUES的 ,如20200401就变成20200401和20200501,其中20200401指定范围,20200501为MAXVALUES

CREATE DEFINER="root"@"%" PROCEDURE "proc_create_partition"(in_tbname VARCHAR(64))
BEGIN

SELECT DATABASE() INTO @dbname;

SET @tbname = in_tbname;

#查询表的最近一次分区的名字,这里按自然数递增的。比如0,1,2,4。去掉 p
SELECT
    REPLACE (partition_name, 'p', '') INTO @PMAX
FROM
    INFORMATION_SCHEMA.PARTITIONS
WHERE
    TABLE_SCHEMA = @dbname
AND table_name = @tbname
ORDER BY
    partition_ordinal_position DESC
LIMIT 1;

#查询表的最近一次分区的指定时间,比如最近时间的分区时 2020.04.01
SELECT
REPLACE(partition_description, '\'', '') INTO @DNAME
FROM
    INFORMATION_SCHEMA.PARTITIONS
WHERE
    TABLE_SCHEMA = @dbname
AND table_name = @tbname
ORDER BY
    partition_ordinal_position DESC
LIMIT 1, 1;


SET @t=CONCAT('alter table `',@dbname,'`.',@tbname,' reorganize partition p',@PMAX,
                          ' into(partition p',@PMAX,' values less than (''',date(DATE_ADD(@DNAME,INTERVAL 1 MONTH)),'''),',
                            'partition p',@PMAX+1,' values less than MAXVALUE)');

SELECT @t;
PREPARE stmt FROM @t;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

COMMIT;
END

这样传入box_fenqu表名即可进行增加分区了: CALL proc_create_partition('box_fenqu');

3、创建mysql事件,定时调度分区;下面的事件到的意思就是从2020-05-03 00:00:00开始,每过一个月就执行下CALL proc_create_partition('box_fenqu')

CREATE DEFINER=`root`@`%` EVENT `e_create_partition` 
ON SCHEDULE EVERY 1 MONTH STARTS '2020-05-03 00:00:00' 
ON COMPLETION PRESERVE DISABLE 
DO CALL proc_create_partition('box_fenqu')

4、如果有多个表需要都需要定时的动态增加分区的话,就可以再写个存储如下,这个存储过程将对当前库下所有分区表都进行遍历,然后增加分区

CREATE DEFINER="root"@"%" PROCEDURE "proc_create_partition_all"()
BEGIN

  DECLARE tbname varchar(32);
  DECLARE tmpSql varchar(256);
  DECLARE done INT DEFAULT FALSE ;

#查询已手动分区的表
    DECLARE part_cursor CURSOR FOR (SELECT DISTINCT table_name FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_SCHEMA = DATABASE() AND partition_expression IS NOT NULL AND table_name NOT LIKE '%bak');
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;

#循环对表添加分区
    OPEN part_cursor;
        myLoop: LOOP
            FETCH part_cursor INTO tbname;
            IF done THEN 
                LEAVE myLoop;
            END IF;
            #调用分区存储过程
            CALL proc_create_partition(tbname);

            COMMIT;
        END LOOP myLoop;
    CLOSE part_cursor;
    
END

5、mysql允许创建的分区数量有限,因此我们可以根据业务的情况。定期删除已经不需要的分区

6、查询有数据的最新日期/月份

select REPLACE(partition_description, '\'', '') date from INFORMATION_SCHEMA.PARTITIONS where TABLE_SCHEMA=SCHEMA() AND TABLE_NAME='box_fenqu' AND table_rows != 0
ORDER BY REPLACE(partition_description, '\'', '') desc limit 1

查询有数据的数据条数

select table_rows from INFORMATION_SCHEMA.PARTITIONS where TABLE_SCHEMA=SCHEMA() AND TABLE_NAME='box_fenqu' AND table_rows != 0
ORDER BY REPLACE(partition_description, '\'', '') desc limit 1

7、插入数据测试,编写存储过程如下

CREATE DEFINER="root"@"localhost" PROCEDURE "insert_box_fq"(in max_num int(10),in sDate datetime,in eDate datetime)
begin
 declare i int default 0;
 /*把autocommit设置成0*/
 set autocommit= 0;
 repeat
 set i=i+1;
  INSERT INTO `test`.`box_fenqu`(`create_by`, `create_time`, `update_by`, `update_time`, `sys_org_code`, `status`, `number`, `zi_number`, `house_address`, `sb_number`, `point_id`, `point`, `confirm`, `last_point`) VALUES (rand_string(3), getDateTime(sDate,eDate), rand_string(3), now(), 'A03', 0, rand_string(3), 'A001', '仓库1', rand_string(3), rand_string(3), NULL, 1, rand_string(3));


 until i=max_num end repeat;
commit;
end

插入10万条 call insert_box_fq(100000,'2019-01-01 00:00:00',now())

8、查询各个分区数据情况

SELECT
    PARTITION_NAME AS '分区名',
    TABLE_ROWS AS '记录数' ,
        PARTITION_DESCRIPTION '范围'
FROM
    information_schema.PARTITIONS 
WHERE
    table_schema = 'test' 
    AND table_name = 'box_fenqu';

思考分区结合分页

既然是按日期分区,那么我们在分页查询时就可以使用日期。如下我查询最新的10条数据。效率非常低,因为需要扫描所有的分区。但是事实上我们只需要10条而已,只需要在最新的分区中查找。若最新的分区记录数大于等于10那么就需要前移动一个月份,另外扫描一个分区。依次类推。我想这样应该查询效率很快了吧

SELECT *  FROM box_fenqu ORDER BY create_time DESC LIMIT 10

这样需要写一个存储过程来查询,将limit 扫描的分区只锁定在需要的分区里

你可能感兴趣的:(mysql 分区实践之按月份分区,定时增加分区和删除分区)