1.概述
分区表在生产环境用的太多太多,当分区增长到一定数量的时候,我们需要将数据归档,我见过有些运维在oracle环境下是这么做的,将需要归档的数据插入到其他表,于是执行这么一条语句:
insert into archive_tbl select * from live_tbl where "过滤条件"/partition 分区名;
当分区数据量有几十个GB,甚至上百GB的时候,这种操作是非常要命的,需要把交换的分区读进来,写出去,浪费非常多的IO资源,对于OLTP缓解来讲,业务高峰期,这是致命的,各种等待事件一下就上来了,搞得你目瞪口呆。
当我们使用分区交换技术的时候,将数据归档到其他表,都是妙级操作,更改的只是数据字典。在mysql中,分区交换技术主要在5.7这个版本有变化,5.7以前和5.7实现交换,略有不同。5.6的分区表交换分区比较绕,而且需要拷贝分区文件,效率不是特别高,不过话又说回来,拷贝分区文件比导入导出快多了,最后,我们也并不是特别推荐在5.6里面使用分区表,mysql内部一个分区使用一个handler,可能最后弊大于利。
本文没有深入分析分区交换的原理,也没有任何拓展。只是看到oracle环境用分区交换比较多,一时兴起记下mysql分区交换。
2.mysql5.7以前分区交换
2.1环境交代
这里我们借用percona的题材
mysql版本:5.6.34
live_tbl:生产表
archive_tbl:归档表
p201203,这个分区准备从生产表交换到归档表
dest_tbl_tmp:临时表
2.2数据准备
CREATE TABLE live_tbl ( some_id bigint(20) NOT NULL AUTO_INCREMENT, summary_date date NOT NULL, PRIMARY KEY (some_id,summary_date) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci /*!50500 PARTITION BY RANGE COLUMNS(summary_date) (PARTITION p201703 VALUES LESS THAN ('2017-04-01') ENGINE = InnoDB, PARTITION p201704 VALUES LESS THAN ('2017-05-01') ENGINE = InnoDB, PARTITION p201705 VALUES LESS THAN ('2017-06-01') ENGINE = InnoDB, PARTITION p201706 VALUES LESS THAN ('2017-07-01') ENGINE = InnoDB, PARTITION future VALUES LESS THAN (MAXVALUE) ENGINE = InnoDB) */; INSERT INTO live_tbl(summary_date) VALUES ('2017-03-15'),('2017-03-18'), ('2017-04-15'),('2017-04-18'), ('2017-05-15'),('2017-05-18'), ('2017-06-15'),('2017-06-18'), ('2017-07-15'),('2017-07-18'); root@localhost [thedb] 01:10:06>>>select * from live_tbl partition(p201703); +---------+--------------+ | some_id | summary_date | +---------+--------------+ | 1 | 2017-03-15 | | 2 | 2017-03-18 | +---------+--------------+ 2 rows in set (0.00 sec) root@localhost [thedb] 01:10:26>>>select * from live_tbl partition(p201704); +---------+--------------+ | some_id | summary_date | +---------+--------------+ | 3 | 2017-04-15 | | 4 | 2017-04-18 | +---------+--------------+ 2 rows in set (0.00 sec) root@localhost [thedb] 01:10:28>>>select * from live_tbl partition(p201705); +---------+--------------+ | some_id | summary_date | +---------+--------------+ | 5 | 2017-05-15 | | 6 | 2017-05-18 | +---------+--------------+ 2 rows in set (0.00 sec) root@localhost [thedb] 01:10:30>>>select * from live_tbl partition(p201706); +---------+--------------+ | some_id | summary_date | +---------+--------------+ | 7 | 2017-06-15 | | 8 | 2017-06-18 | +---------+--------------+ 2 rows in set (0.00 sec) root@localhost [thedb] 01:10:34>>>select * from live_tbl partition(future); +---------+--------------+ | some_id | summary_date | +---------+--------------+ | 9 | 2017-07-15 | | 10 | 2017-07-18 | +---------+--------------+ 2 rows in set (0.00 sec) CREATE TABLE archive_tbl ( some_id bigint(20) NOT NULL AUTO_INCREMENT, summary_date date NOT NULL, PRIMARY KEY (some_id,summary_date) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci /*!50500 PARTITION BY RANGE COLUMNS(summary_date) (PARTITION p201609 VALUES LESS THAN ('2016-10-01') ENGINE = InnoDB, PARTITION p201610 VALUES LESS THAN ('2016-11-01') ENGINE = InnoDB, PARTITION p201611 VALUES LESS THAN ('2016-12-01') ENGINE = InnoDB, PARTITION p201612 VALUES LESS THAN ('2017-01-01') ENGINE = InnoDB, PARTITION p201701 VALUES LESS THAN ('2017-02-01') ENGINE = InnoDB, PARTITION p201702 VALUES LESS THAN ('2017-03-01') ENGINE = InnoDB, PARTITION future VALUES LESS THAN (MAXVALUE) ENGINE = InnoDB) */; INSERT INTO archive_tbl(summary_date) VALUES ('2016-09-15'),('2016-09-18'), ('2016-10-15'),('2016-10-18'), ('2016-11-15'),('2016-11-18'), ('2016-12-15'),('2016-12-18'), ('2017-01-15'),('2017-01-18'), ('2017-02-15'),('2017-02-18'); root@localhost [thedb] 01:47:42>>>select * from archive_tbl partition(p201609); +---------+--------------+ | some_id | summary_date | +---------+--------------+ | 1 | 2016-09-15 | | 2 | 2016-09-18 | +---------+--------------+ 2 rows in set (0.00 sec) root@localhost [thedb] 01:47:42>>>select * from archive_tbl partition(p201610); +---------+--------------+ | some_id | summary_date | +---------+--------------+ | 3 | 2016-10-15 | | 4 | 2016-10-18 | +---------+--------------+ 2 rows in set (0.00 sec) root@localhost [thedb] 01:47:42>>>select * from archive_tbl partition(p201611); +---------+--------------+ | some_id | summary_date | +---------+--------------+ | 5 | 2016-11-15 | | 6 | 2016-11-18 | +---------+--------------+ 2 rows in set (0.00 sec) root@localhost [thedb] 01:47:42>>>select * from archive_tbl partition(p201612); +---------+--------------+ | some_id | summary_date | +---------+--------------+ | 7 | 2016-12-15 | | 8 | 2016-12-18 | +---------+--------------+ 2 rows in set (0.00 sec) root@localhost [thedb] 01:47:42>>>select * from archive_tbl partition(p201701); +---------+--------------+ | some_id | summary_date | +---------+--------------+ | 9 | 2017-01-15 | | 10 | 2017-01-18 | +---------+--------------+ 2 rows in set (0.00 sec) root@localhost [thedb] 01:47:42>>>select * from archive_tbl partition(p201702); +---------+--------------+ | some_id | summary_date | +---------+--------------+ | 11 | 2017-02-15 | | 12 | 2017-02-18 | +---------+--------------+ 2 rows in set (0.00 sec)
2.2 创建临时表
mysql> CREATE TABLE dest_tbl_tmp LIKE archive_tbl; mysql> ALTER TABLE dest_tbl_tmp REMOVE PARTITIONING; mysql> ALTER TABLE dest_tbl_tmp DISCARD TABLESPACE;
2.3 拷贝交换分区
为了保证数据一致性,我们flush一下。
mysql> USE thedb mysql> FLUSH TABLE live_tbl FOR EXPORT;
然后在操作系统层面讲分区拷贝到临时表:
shell> cp live_tbl#P#p201703.ibd dest_tbl_tmp.ibd
然后import分区到临时表
mysql> unlock tables; mysql> ALTER TABLE dest_tbl_tmp IMPORT TABLESPACE;
2.4 归档表添加分区
mysql> ALTER TABLE archive_tbl REORGANIZE PARTITION future INTO ( PARTITION p201703 VALUES LESS THAN ('2017-04-01'), PARTITION future VALUES LESS THAN (MAXVALUE) );
2.5 进行分区交换
mysql> ALTER TABLE archive_tbl EXCHANGE PARTITION p201703 WITH TABLE dest_tbl_tmp;
2.6 删除久分区,并验证
root@localhost [thedb] 02:17:06>>>select * from archive_tbl partition(p201703); +---------+--------------+ | some_id | summary_date | +---------+--------------+ | 1 | 2017-03-15 | | 2 | 2017-03-18 | +---------+--------------+ 2 rows in set (0.01 sec) mysql>ALTER TABLE live_tbl DROP PARTITION p201703;
3. mysql5.7的分区交换
5.7的分区交换相对容易,不需要那么绕。
表和数据准备:
CREATE TABLE e ( id INT NOT NULL, fname VARCHAR(30), lname VARCHAR(30) ) PARTITION BY RANGE (id) ( PARTITION p0 VALUES LESS THAN (50), PARTITION p1 VALUES LESS THAN (100), PARTITION p2 VALUES LESS THAN (150), PARTITION p3 VALUES LESS THAN (MAXVALUE) ); INSERT INTO e VALUES (1669, "Jim", "Smith"), (337, "Mary", "Jones"), (16, "Frank", "White"), (2005, "Linda", "Black");
准备一张结构相似的非分区表:
CREATE TABLE e2 LIKE e; ALTER TABLE e2 REMOVE PARTITIONING;
各个分区数据的分布:
root@localhost [db01] 03:47:50>>>SELECT PARTITION_NAME, TABLE_ROWS FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_NAME = 'e'; +----------------+------------+ | PARTITION_NAME | TABLE_ROWS | +----------------+------------+ | p0 | 1 | | p1 | 0 | | p2 | 0 | | p3 | 3 | +----------------+------------+ 4 rows in set (0.00 sec)
将P0分区交换出去
ALTER TABLE e EXCHANGE PARTITION p0 WITH TABLE e2;
验证现有数据:
root@localhost [db01] 03:48:56>>>SELECT PARTITION_NAME, TABLE_ROWS FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_NAME = 'e'; +----------------+------------+ | PARTITION_NAME | TABLE_ROWS | +----------------+------------+ | p0 | 0 | | p1 | 0 | | p2 | 0 | | p3 | 2 | +----------------+------------+ 4 rows in set (0.00 sec)
查看交换出来的数据:
root@localhost [db01] 03:48:57>>>SELECT * FROM e2; +----+-------+-------+ | id | fname | lname | +----+-------+-------+ | 16 | Frank | White | +----+-------+-------+ 1 row in set (0.00 sec)