MySQL-表分区

一、表分区介绍

1.1、简单介绍

1)表分区是将一个表的数据按照一定的规则水平划分为不同的逻辑块,并分别进行物理存储,这个规则就叫做分区函数,可以有不同的分区规则

2)通过show plugins语句查看当前MySQL是否支持表分区功能

mysql> show plugins;
+----------------------------+----------+--------------------+---------+---------+
| Name                       | Status   | Type               | Library | License |
+----------------------------+----------+--------------------+---------+---------+
| binlog                     | ACTIVE   | STORAGE ENGINE     | NULL    | GPL     |
| mysql_native_password      | ACTIVE   | AUTHENTICATION     | NULL    | GPL     |
| sha256_password            | ACTIVE   | AUTHENTICATION     | NULL    | GPL     |
| PERFORMANCE_SCHEMA         | ACTIVE   | STORAGE ENGINE     | NULL    | GPL     |
| MRG_MYISAM                 | ACTIVE   | STORAGE ENGINE     | NULL    | GPL     |
| MEMORY                     | ACTIVE   | STORAGE ENGINE     | NULL    | GPL     |
| InnoDB                     | ACTIVE   | STORAGE ENGINE     | NULL    | GPL     |
| INNODB_TRX                 | ACTIVE   | INFORMATION SCHEMA | NULL    | GPL     |
| INNODB_LOCKS               | ACTIVE   | INFORMATION SCHEMA | NULL    | GPL     |
| INNODB_LOCK_WAITS          | ACTIVE   | INFORMATION SCHEMA | NULL    | GPL     |
| INNODB_CMP                 | ACTIVE   | INFORMATION SCHEMA | NULL    | GPL     |
| INNODB_CMP_RESET           | ACTIVE   | INFORMATION SCHEMA | NULL    | GPL     |
| INNODB_CMPMEM              | ACTIVE   | INFORMATION SCHEMA | NULL    | GPL     |
| INNODB_CMPMEM_RESET        | ACTIVE   | INFORMATION SCHEMA | NULL    | GPL     |
| INNODB_CMP_PER_INDEX       | ACTIVE   | INFORMATION SCHEMA | NULL    | GPL     |
| INNODB_CMP_PER_INDEX_RESET | ACTIVE   | INFORMATION SCHEMA | NULL    | GPL     |
| INNODB_BUFFER_PAGE         | ACTIVE   | INFORMATION SCHEMA | NULL    | GPL     |
| INNODB_BUFFER_PAGE_LRU     | ACTIVE   | INFORMATION SCHEMA | NULL    | GPL     |
| INNODB_BUFFER_POOL_STATS   | ACTIVE   | INFORMATION SCHEMA | NULL    | GPL     |
| INNODB_TEMP_TABLE_INFO     | ACTIVE   | INFORMATION SCHEMA | NULL    | GPL     |
| INNODB_METRICS             | ACTIVE   | INFORMATION SCHEMA | NULL    | GPL     |
| INNODB_FT_DEFAULT_STOPWORD | ACTIVE   | INFORMATION SCHEMA | NULL    | GPL     |
| INNODB_FT_DELETED          | ACTIVE   | INFORMATION SCHEMA | NULL    | GPL     |
| INNODB_FT_BEING_DELETED    | ACTIVE   | INFORMATION SCHEMA | NULL    | GPL     |
| INNODB_FT_CONFIG           | ACTIVE   | INFORMATION SCHEMA | NULL    | GPL     |
| INNODB_FT_INDEX_CACHE      | ACTIVE   | INFORMATION SCHEMA | NULL    | GPL     |
| INNODB_FT_INDEX_TABLE      | ACTIVE   | INFORMATION SCHEMA | NULL    | GPL     |
| INNODB_SYS_TABLES          | ACTIVE   | INFORMATION SCHEMA | NULL    | GPL     |
| INNODB_SYS_TABLESTATS      | ACTIVE   | INFORMATION SCHEMA | NULL    | GPL     |
| INNODB_SYS_INDEXES         | ACTIVE   | INFORMATION SCHEMA | NULL    | GPL     |
| INNODB_SYS_COLUMNS         | ACTIVE   | INFORMATION SCHEMA | NULL    | GPL     |
| INNODB_SYS_FIELDS          | ACTIVE   | INFORMATION SCHEMA | NULL    | GPL     |
| INNODB_SYS_FOREIGN         | ACTIVE   | INFORMATION SCHEMA | NULL    | GPL     |
| INNODB_SYS_FOREIGN_COLS    | ACTIVE   | INFORMATION SCHEMA | NULL    | GPL     |
| INNODB_SYS_TABLESPACES     | ACTIVE   | INFORMATION SCHEMA | NULL    | GPL     |
| INNODB_SYS_DATAFILES       | ACTIVE   | INFORMATION SCHEMA | NULL    | GPL     |
| INNODB_SYS_VIRTUAL         | ACTIVE   | INFORMATION SCHEMA | NULL    | GPL     |
| CSV                        | ACTIVE   | STORAGE ENGINE     | NULL    | GPL     |
| MyISAM                     | ACTIVE   | STORAGE ENGINE     | NULL    | GPL     |
| ARCHIVE                    | ACTIVE   | STORAGE ENGINE     | NULL    | GPL     |
| partition                  | ACTIVE   | STORAGE ENGINE     | NULL    | GPL     |  #表分区是支持的
| BLACKHOLE                  | ACTIVE   | STORAGE ENGINE     | NULL    | GPL     |
| FEDERATED                  | DISABLED | STORAGE ENGINE     | NULL    | GPL     |
| ngram                      | ACTIVE   | FTPARSER           | NULL    | GPL     |
+----------------------------+----------+--------------------+---------+---------+

3)MySQL5.7社区版本的表分区功能是默认开启的

1.2、创建一个表分区示例

#创建分区表
CREATE TABLE employees ( 
 id INT NOT NULL,
 fname VARCHAR(30), 
 lname VARCHAR(30),
 hired DATE NOT NULL DEFAULT '1970-01-01',
 separated DATE NOT NULL DEFAULT '9999-12-31',
 job_code INT NOT NULL,
 store_id INT NOT NULL )
 PARTITION BY RANGE (store_id)  #根据store_id范围分区
 ( PARTITION p0 VALUES LESS THAN (6), PARTITION p1 VALUES LESS THAN (11),
PARTITION p2 VALUES LESS THAN (16), PARTITION p3 VALUES LESS THAN (21) );

#查看表分区结构
mysql> show create table employees\G
*************************** 1. row ***************************
       Table: employees
Create Table: CREATE TABLE `employees` (
  `id` int(11) NOT NULL,
  `fname` varchar(30) DEFAULT NULL,
  `lname` varchar(30) DEFAULT NULL,
  `hired` date NOT NULL DEFAULT '1970-01-01',
  `separated` date NOT NULL DEFAULT '9999-12-31',
  `job_code` int(11) NOT NULL,
  `store_id` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8
/*!50100 PARTITION BY RANGE (store_id)
(PARTITION p0 VALUES LESS THAN (6) ENGINE = InnoDB,
 PARTITION p1 VALUES LESS THAN (11) ENGINE = InnoDB,
 PARTITION p2 VALUES LESS THAN (16) ENGINE = InnoDB,
 PARTITION p3 VALUES LESS THAN (21) ENGINE = InnoDB) */

#插入数据测试
mysql> insert into employees values(1,'a','a',now(),now(),1,1),(2,'b','b',now(),now(),1,6);

#查看
mysql> select * from employees;
+----+-------+-------+------------+------------+----------+----------+
| id | fname | lname | hired      | separated  | job_code | store_id |
+----+-------+-------+------------+------------+----------+----------+
|  1 | a     | a     | 2019-10-10 | 2019-10-10 |        1 |        1 |
|  2 | b     | b     | 2019-10-10 | 2019-10-10 |        1 |        6 |
+----+-------+-------+------------+------------+----------+----------+

mysql> select * from employees partition (p0); #落在第一个分区上store_id<6
+----+-------+-------+------------+------------+----------+----------+
| id | fname | lname | hired      | separated  | job_code | store_id |
+----+-------+-------+------------+------------+----------+----------+
|  1 | a     | a     | 2019-10-10 | 2019-10-10 |        1 |        1 |
+----+-------+-------+------------+------------+----------+----------+

mysql> select * from employees partition (p1);
+----+-------+-------+------------+------------+----------+----------+
| id | fname | lname | hired      | separated  | job_code | store_id |
+----+-------+-------+------------+------------+----------+----------+
|  2 | b     | b     | 2019-10-10 | 2019-10-10 |        1 |        6 |
+----+-------+-------+------------+------------+----------+----------+

1.3、表分区创建注意事项

当表中含有主键或唯一键时,则每个被用作分区函数的字段必须是表中唯一键和主键的全部或一部分,否则就无法创建分区表

#比如下面的表由于唯一键和主键没有相同的字段,所以无法创建表分区
CREATE TABLE tnp (
id INT NOT NULL AUTO_INCREMENT,
ref BIGINT NOT NULL,
name VARCHAR(255),
PRIMARY KEY pk (id),
UNIQUE KEY uk (ref) )
PARTITION BY RANGE (id)
 ( PARTITION p0 VALUES LESS THAN (6), PARTITION p1 VALUES LESS THAN (11));
#ERROR 1503 (HY000): A UNIQUE INDEX must include all columns in the table's partitioning function

CREATE TABLE tnp (id INT NOT NULL AUTO_INCREMENT,
ref BIGINT NOT NULL,
name VARCHAR(255),
PRIMARY KEY pk (id),
UNIQUE KEY uk (ref) )
PARTITION BY RANGE (ref)
 ( PARTITION p0 VALUES LESS THAN (6), PARTITION p1 VALUES LESS THAN (11));
#ERROR 1503 (HY000): A PRIMARY KEY must include all columns in the table's partitioning function
---------------------------------------------------------------------------------------------
#解决方法
#上述例子中删除唯一键,确保主键中的字段包含分区函数中的所有字段,创建成功
CREATE TABLE tnp (
id INT NOT NULL AUTO_INCREMENT,
ref BIGINT NOT NULL,
name VARCHAR(255),
PRIMARY KEY pk (id))
PARTITION BY RANGE (id)
 ( PARTITION p0 VALUES LESS THAN (6), PARTITION p1 VALUES LESS THAN (11));

#或者将主键扩展为包含ref字段(联合主键)
CREATE TABLE tnp (id INT NOT NULL ,
ref BIGINT NOT NULL,
name VARCHAR(255),
PRIMARY KEY pk (id,ref),
UNIQUE KEY uk (ref) )
PARTITION BY RANGE (ref)
( PARTITION p0 VALUES LESS THAN (6), PARTITION p1 VALUES LESS THAN (11));

1.4、表分区优势

表分区的主要优势在于:

1)可以允许在一个表里存储更多的数据,突破磁盘限制或者文件系统限制

2)对于从表里将过期或历史的数据移除在表分区很容易实现,只要将对应的分区移除即可

3)对某些查询和修改语句来说,可以自动将数据范围缩小到一个或几个表分区上,优化语句执行效率。而且可以通过显示指定表分区来执行语句,比如SELECT * FROM t PARTITION (p0,p1) WHERE c < 5

mysql> explain select * from employees;
+----+-------------+-----------+-------------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table     | partitions  | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra |
+----+-------------+-----------+-------------+------+---------------+------+---------+------+------+----------+-------+
|  1 | SIMPLE      | employees | p0,p1,p2,p3 | ALL  | NULL          | NULL | NULL    | NULL |    2 |   100.00 | NULL  |
+----+-------------+-----------+-------------+------+---------------+------+---------+------+------+----------+-------+

mysql> explain select * from employees where store_id <6;
+----+-------------+-----------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table     | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-----------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | employees | p0         | ALL  | NULL          | NULL | NULL    | NULL |    1 |   100.00 | Using where |
+----+-------------+-----------+------------+------+---------------+------+---------+------+------+----------+-------------+

1.5、表分区底层结构

[root@db01 data]# cd part/
[root@db01 part]# ls
db.opt  employees.frm  employees#P#p0.ibd  employees#P#p1.ibd  employees#P#p2.ibd  employees#P#p3.ibd  tnp.frm  tnp#P#p0.ibd  tnp#P#p1.ibd
[root@db01 part]# ls employees*
employees.frm  employees#P#p0.ibd  employees#P#p1.ibd  employees#P#p2.ibd  employees#P#p3.ibd
#根据不同分区有不同的.ibd文件

1.6、表分区类型

表分区类型分为:

  • RANGE表分区:范围表分区,按照一定的范围值来确定每个分区包含的数据
  • LIST表分区:列表表分区,按照一个一个确定的值来确定每个分区包含的数据
  • HASH表分区:哈希表分区,按照一个自定义的函数返回值来确定每个分区包含的数据
  • KEY表分区 :key表分区,与哈希表分区类似,只是用MySQL自己的HASH函数来确定每个分区包含的数据

二、RANGE表分区

1)RANGE表分区:范围表分区,按照一定的范围值来确定每个分区包含的数据,分区函数使用的字段必须只能是整数类型

2)分区的定义范围必须是连续的,且不能有重叠部分,通过使用VALUES LESS THAN来定义分区范围,表分区的范围定义是从小到大定义

3)对range和list表分区来说,分区函数可以包含多个字段。分区多字段函数所涉及的字段类型可以包括:

  • TINYINT, SMALLINT, MEDIUMINT, INT (INTEGER), and BIGINT.
  • DATE and DATETIME.
  • CHAR, VARCHAR, BINARY, and VARBINARY.
  • 其他的字段类型都不支持

image

2.1、普通范围表分区

CREATE TABLE employees ( 
id INT NOT NULL,
fname VARCHAR(30), 
lname VARCHAR(30),
hired DATE NOT NULL DEFAULT '1970-01-01',
separated DATE NOT NULL DEFAULT '9999-12-31',
job_code INT NOT NULL,
store_id INT NOT NULL )
PARTITION BY RANGE (store_id)
( PARTITION p0 VALUES LESS THAN (6), 
PARTITION p1 VALUES LESS THAN (11), 
PARTITION p2 VALUES LESS THAN (16), 
PARTITION p3 VALUES LESS THAN (21) );

#Store_id<6的数据被放在p0分区里,6<=store_id<10之间的数据被放在p1分区里,以此类推
当新插入的数据为(72, 'Mitchell', 'Wilson', '1998-06-25','2000-08-09', 1, 13) 时,则新数据被插入到p2分区里
mysql> insert into employees values(72, 'Mitchell', 'Wilson', '1998-06-25','2000-08-09', 1, 13);
mysql> select * from employees partition(p2);
+----+----------+--------+------------+------------+----------+----------+
| id | fname    | lname  | hired      | separated  | job_code | store_id |
+----+----------+--------+------------+------------+----------+----------+
| 72 | Mitchell | Wilson | 1998-06-25 | 2000-08-09 |        1 |       13 |
+----+----------+--------+------------+------------+----------+----------+

#但当插入的数据的store_id为21时,由于没有分区去容纳此数据,所以会报错,我们需要修改一下表的定义
mysql> insert into employees values(4,'d','d',now(),now(),1,21);
ERROR 1526 (HY000): Table has no partition for value 21

#分区必须顺序增加,否则报错
CREATE TABLE employees ( 
id INT NOT NULL,
fname VARCHAR(30), 
lname VARCHAR(30),
hired DATE NOT NULL DEFAULT '1970-01-01',
separated DATE NOT NULL DEFAULT '9999-12-31',
job_code INT NOT NULL,
store_id INT NOT NULL )
PARTITION BY RANGE (store_id)
 ( PARTITION p0 VALUES LESS THAN (6),PARTITION p1 VALUES LESS THAN (16),
PARTITION p2 VALUES LESS THAN (11), PARTITION p3 VALUES LESS THAN (21) );
#ERROR 1493 (HY000): VALUES LESS THAN value must be strictly increasing for each partition

#分区函数类型必须是整数
CREATE TABLE employees2( 
id INT NOT NULL,
fname VARCHAR(30), 
lname VARCHAR(30),
hired DATE NOT NULL DEFAULT '1970-01-01',
separated DATE NOT NULL DEFAULT '9999-12-31',
job_code INT NOT NULL,
store_id INT NOT NULL )
PARTITION BY RANGE (name)
 ( PARTITION p0 VALUES LESS THAN ('a'), PARTITION p1 VALUES LESS THAN ('b'),
PARTITION p2 VALUES LESS THAN ('c'), PARTITION p3 VALUES LESS THAN ('d') );
#ERROR 1697 (HY000): VALUES value for partition 'p0' must have type INT


#MAXVALUE关键词的作用是表示可能的最大值,所以任何store_id>=16的数据都会被写入到p3分区里
CREATE TABLE employees ( 
id INT NOT NULL, 
fname VARCHAR(30), 
lname VARCHAR(30),
hired DATE NOT NULL DEFAULT '1970-01-01', 
separated DATE NOT NULL DEFAULT '9999-12-31', 
job_code INT NOT NULL, store_id INT NOT NULL )
PARTITION BY RANGE (store_id)
( PARTITION p0 VALUES LESS THAN (6),
PARTITION p1 VALUES LESS THAN (11),
PARTITION p2 VALUES LESS THAN (16),
PARTITION p3 VALUES LESS THAN MAXVALUE
);

mysql> insert into employees values(4,'d','d',now(),now(),1,21);
mysql> select * from employees partition(p3);
+----+-------+-------+------------+------------+----------+----------+
| id | fname | lname | hired      | separated  | job_code | store_id |
+----+-------+-------+------------+------------+----------+----------+
|  4 | d     | d     | 2019-10-10 | 2019-10-10 |        1 |       21 |
+----+-------+-------+------------+------------+----------+----------+


#分区函数中也可以使用表达式,比如:
CREATE TABLE employees ( 
id INT NOT NULL, 
fname VARCHAR(30), 
lname VARCHAR(30),
hired DATE NOT NULL DEFAULT '1970-01-01', 
separated DATE NOT NULL DEFAULT '9999-12-31', 
job_code INT, 
store_id INT )
PARTITION BY RANGE ( YEAR(separated) )  #按年份来进行分区
( PARTITION p0 VALUES LESS THAN (1991),
PARTITION p1 VALUES LESS THAN (1996),
PARTITION p2 VALUES LESS THAN (2001),
PARTITION p3 VALUES LESS THAN MAXVALUE );


#对timestamp字段类型可以使用的表达式目前仅有unix_timestamp,其他的表达式都不允许
CREATE TABLE quarterly_report_status ( 
report_id INT NOT NULL, 
report_status VARCHAR(20) NOT NULL, 
report_updated TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP )
PARTITION BY RANGE ( UNIX_TIMESTAMP(report_updated) )
( PARTITION p0 VALUES LESS THAN ( UNIX_TIMESTAMP('2008-01-01 00:00:00') ),
PARTITION p1 VALUES LESS THAN ( UNIX_TIMESTAMP('2008-04-01 00:00:00') ),
PARTITION p2 VALUES LESS THAN ( UNIX_TIMESTAMP('2008-07-01 00:00:00') ),
PARTITION p3 VALUES LESS THAN ( UNIX_TIMESTAMP('2008-10-01 00:00:00') ),
PARTITION p4 VALUES LESS THAN ( UNIX_TIMESTAMP('2009-01-01 00:00:00') ),
PARTITION p5 VALUES LESS THAN ( UNIX_TIMESTAMP('2009-04-01 00:00:00') ),
PARTITION p6 VALUES LESS THAN ( UNIX_TIMESTAMP('2009-07-01 00:00:00') ),
PARTITION p7 VALUES LESS THAN ( UNIX_TIMESTAMP('2009-10-01 00:00:00') ),
PARTITION p8 VALUES LESS THAN ( UNIX_TIMESTAMP('2010-01-01 00:00:00') ),
PARTITION p9 VALUES LESS THAN (MAXVALUE) );

mysql> create table temp(tstamp timestamp) partition by range(year(tstamp))(partition p0 values less than(2017));
ERROR 1486 (HY000): Constant, random or timezone-dependent expressions in (sub)partitioning function are not allowed
mysql> create table temp(tstamp datetime) partition by range(year(tstamp))(partition p0 values less than(2017));
Query OK, 0 rows affected (0.01 sec)

2.2、范围多字段表分区

1)范围多字段分区函数与普通的范围分区函数的区别在于:

  • a)字段类型多样化
  • b)范围多字段分区函数不支持表达式,只能用字段名
  • c)范围多字段分区函数支持一个或多个字段

image

2)每个column_list里的字段和value_list里的数值必须一一对应,数据类型也要一致

3)对范围多字段分区来说,有时一行数据的分区列表的第一个元素等于VALUES LESS THAN的值列表的第一个元素是会被插入到相应的分区

CREATE TABLE rcx (
a INT, 
b INT,
c CHAR(3), 
d INT)
PARTITION BY RANGE COLUMNS(a,b,c)
(PARTITION p0 VALUES LESS THAN (5,10,'ggg'),
PARTITION p1 VALUES LESS THAN (10,20,'mmm'),
PARTITION p2 VALUES LESS THAN (15,30,'sss'),
PARTITION p3 VALUES LESS THAN (MAXVALUE,MAXVALUE,MAXVALUE));

mysql> insert into rcx values(4,5,'abc',1),(5,9,'abc',1),(4,11,'ggg',1),(5,11,'abc',1),(6,2, 'abc',1);
mysql> select * from rcx partition (p0);
+------+------+------+------+
| a    | b    | c    | d    |
+------+------+------+------+
|    4 |    5 | abc  |    1 |
|    5 |    9 | abc  |    1 |
|    4 |   11 | ggg  |    1 |
+------+------+------+------+

mysql> select * from rcx partition (p1);
+------+------+------+------+
| a    | b    | c    | d    |
+------+------+------+------+
|    5 |   11 | abc  |    1 |
|    6 |    2 | abc  |    1 |
+------+------+------+------+


#再比如创建如下的表分区:
CREATE TABLE rc1 ( 
a INT, 
b INT )
PARTITION BY RANGE COLUMNS(a, b)
( PARTITION p0 VALUES LESS THAN (5, 12),
PARTITION p1 VALUES LESS THAN (MAXVALUE, MAXVALUE) );

mysql> INSERT INTO rc1 VALUES (5,10), (5,11), (5,12);
mysql> SELECT TABLE_SCHEMA,PARTITION_NAME,TABLE_ROWS FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_NAME = 'rc1';
+--------------+----------------+------------+
| TABLE_SCHEMA | PARTITION_NAME | TABLE_ROWS |
+--------------+----------------+------------+
| part         | p0             |          2 |
| part         | p1             |          1 |
+--------------+----------------+------------+

#对多列对比来说:
mysql> SELECT (5,10) < (5,12), (5,11) < (5,12), (5,12) < (5,12);
+-----------------+-----------------+-----------------+
| (5,10) < (5,12) | (5,11) < (5,12) | (5,12) < (5,12) |
+-----------------+-----------------+-----------------+
|               1 |               1 |               0 |
+-----------------+-----------------+-----------------+

#当然只要保证取值范围是增长的,表分区就能创建成功,比如:
CREATE TABLE rc4 ( a INT, b INT, c INT )
PARTITION BY RANGE COLUMNS(a,b,c)
( PARTITION p0 VALUES LESS THAN (0,25,50),
PARTITION p1 VALUES LESS THAN (10,20,100),
PARTITION p2 VALUES LESS THAN (10,30,50),
PARTITION p3 VALUES LESS THAN (MAXVALUE,MAXVALUE,MAXVALUE) );

#但如果取值范围不是增长的,就会返回错误:
CREATE TABLE rcf (a INT, b INT, c INT)
PARTITION BY RANGE COLUMNS(a,b,c) (
PARTITION p0 VALUES LESS THAN (0,25,50),
PARTITION p1 VALUES LESS THAN (20,20,100),
PARTITION p2 VALUES LESS THAN (10,30,50),
PARTITION p3 VALUES LESS THAN (MAXVALUE,MAXVALUE,MAXVALUE));
#ERROR 1493 (HY000): VALUES LESS THAN value must be strictly increasing for each partition


#对其他数据类型的支持:
CREATE TABLE employees_by_lname ( 
id INT NOT NULL, 
fname VARCHAR(30),
lname VARCHAR(30), 
hired DATE NOT NULL DEFAULT '1970-01-01',
separated DATE NOT NULL DEFAULT '9999-12-31',
job_code INT NOT NULL, 
store_id INT NOT NULL )
PARTITION BY RANGE COLUMNS (lname)
( PARTITION p0 VALUES LESS THAN ('g'),
PARTITION p1 VALUES LESS THAN ('m'),
PARTITION p2 VALUES LESS THAN ('t'),
PARTITION p3 VALUES LESS THAN (MAXVALUE) );

CREATE TABLE employees_by_lname ( 
id INT NOT NULL, 
fname VARCHAR(30),
lname VARCHAR(30), 
hired DATE NOT NULL DEFAULT '1970-01-01',
separated DATE NOT NULL DEFAULT '9999-12-31',
job_code INT NOT NULL, 
store_id INT NOT NULL )
PARTITION BY RANGE COLUMNS (hired)
( PARTITION p0 VALUES LESS THAN ('1970-01-01'),
PARTITION p1 VALUES LESS THAN ('1980-01-01'),
PARTITION p2 VALUES LESS THAN ('1990-01-01'),
PARTITION p3 VALUES LESS THAN ('2000-01-01'),
PARTITION p4 VALUES LESS THAN ('2010-01-01'),
PARTITION p5 VALUES LESS THAN (MAXVALUE) );

三、LIST表分区

1)LIST表分区:列表表分区,按照一个一个确定的值来确定每个分区包含的数据

2)通过PARTITION BY LIST(expr)分区函数表达式必须返回整数,取值范围通过VALUES IN (value_list)定义

3.1、普通LIST表分区

CREATE TABLE employees ( 
id INT NOT NULL, 
fname VARCHAR(30), 
lname VARCHAR(30),
hired DATE NOT NULL DEFAULT '1970-01-01',
separated DATE NOT NULL DEFAULT '9999-12-31',
job_code INT, store_id INT )
PARTITION BY LIST(store_id)
( PARTITION pNorth VALUES IN (3,5,6,9,17),
PARTITION pEast VALUES IN (1,2,10,11,19,20),
PARTITION pWest VALUES IN (4,12,13,14,18),
PARTITION pCentral VALUES IN (7,8,15,16) );

#报错
CREATE TABLE employees (
id INT NOT NULL, 
fname VARCHAR(30), 
lname VARCHAR(30),
hired DATE NOT NULL DEFAULT '1970-01-01',
separated DATE NOT NULL DEFAULT '9999-12-31',
job_code INT, store_name varchar(20) )
PARTITION BY LIST(store_name)
 ( PARTITION pNorth VALUES IN ('a','b'),
PARTITION pEast VALUES IN ('c','d'));
#ERROR 1697 (HY000): VALUES value for partition 'pNorth' must have type INT


#对List表分区来说,没有MAXVALUE特殊值,所有的可能取值都需要再VALUES IN中包含,如果有定义的取值则会报错
CREATE TABLE h2 (c1 INT, c2 INT )
PARTITION BY LIST(c1)
(PARTITION p0 VALUES IN (1, 4, 7),
PARTITION p1 VALUES IN (2, 5, 8));

mysql> INSERT INTO h2 VALUES (3, 5);
ERROR 1525 (HY000): Table has no partition for value 3


#同样,当有主键或者唯一键存在的情况下,分区函数字段需要包含在主键或唯一键中
CREATE TABLE employees ( 
id INT NOT NULL primary key, 
fname VARCHAR(30), 
lname VARCHAR(30),
hired DATE NOT NULL DEFAULT '1970-01-01',
separated DATE NOT NULL DEFAULT '9999-12-31',
job_code INT, store_id int )
PARTITION BY LIST(store_id)
 ( PARTITION pNorth VALUES IN (1,3),
PARTITION pEast VALUES IN (2,4));
#ERROR 1503 (HY000): A PRIMARY KEY must include all columns in the table's partitioning function

3.2、list列表多字段表分区

CREATE TABLE customers_1
( first_name VARCHAR(25), 
last_name VARCHAR(25),
street_1 VARCHAR(30), 
street_2 VARCHAR(30),
city VARCHAR(15), 
renewal DATE )
PARTITION BY LIST COLUMNS(city)
( PARTITION pRegion_1 VALUES IN('Oskarshamn', 'Högsby', 'Mönsterås'),
PARTITION pRegion_2 VALUES IN('Vimmerby', 'Hultsfred', 'Västervik'),
PARTITION pRegion_3 VALUES IN('Nässjö', 'Eksjö', 'Vetlanda'),
PARTITION pRegion_4 VALUES IN('Uppvidinge', 'Alvesta', 'Växjo') );

四、哈希表分区

1)哈希表分区,按照一个自定义的函数返回值来确定每个分区包含的数据,这个自定义函数也可以仅仅是一个字段名字

2)通过PARTITION BY HASH (expr)子句来表达哈希表分区,其中的expr表达式必须返回一个整数基于分区个数的取模(%)运算。根据余数插入到指定的分区

3)对哈希表分区来说只需要定义分区的个数,其他的事情由内部完成

CREATE TABLE employees ( 
id INT NOT NULL, 
fname VARCHAR(30),
lname VARCHAR(30), 
hired DATE NOT NULL DEFAULT '1970-01-01',
separated DATE NOT NULL DEFAULT '9999-12-31',
job_code INT, 
store_id INT )
PARTITION BY HASH(store_id)
PARTITIONS 4;

#如果没有写明PARTITIONS字段,则默认为1
#表达式可以是整数类型字段,也可以是一个函数,比如
CREATE TABLE employees ( 
id INT NOT NULL, 
fname VARCHAR(30),
lname VARCHAR(30), 
hired DATE NOT NULL DEFAULT '1970-01-01',
separated DATE NOT NULL DEFAULT '9999-12-31', 
job_code INT, store_id INT )
PARTITION BY HASH( YEAR(hired) )
PARTITIONS 4;


mysql> insert into employees values(1,'a','a',now(),now(),1,1);
mysql> insert into employees values(1,'a','a',now(),now(),1,2);
mysql> insert into employees values(1,'a','a',now(),now(),1,3);
mysql> insert into employees values(1,'a','a',now(),now(),1,4);
mysql> insert into employees values(1,'a','a',now(),now(),1,5);
mysql> select * from employees partition(p0);
+----+-------+-------+------------+------------+----------+----------+
| id | fname | lname | hired      | separated  | job_code | store_id |
+----+-------+-------+------------+------------+----------+----------+
|  1 | a     | a     | 2019-10-10 | 2019-10-10 |        1 |        4 |
+----+-------+-------+------------+------------+----------+----------+

mysql> select * from employees partition(p1);
+----+-------+-------+------------+------------+----------+----------+
| id | fname | lname | hired      | separated  | job_code | store_id |
+----+-------+-------+------------+------------+----------+----------+
|  1 | a     | a     | 2019-10-10 | 2019-10-10 |        1 |        1 |
|  1 | a     | a     | 2019-10-10 | 2019-10-10 |        1 |        5 |
+----+-------+-------+------------+------------+----------+----------+

mysql> select * from employees partition(p2);
+----+-------+-------+------------+------------+----------+----------+
| id | fname | lname | hired      | separated  | job_code | store_id |
+----+-------+-------+------------+------------+----------+----------+
|  1 | a     | a     | 2019-10-10 | 2019-10-10 |        1 |        2 |
+----+-------+-------+------------+------------+----------+----------+

mysql> select * from employees partition(p3);
+----+-------+-------+------------+------------+----------+----------+
| id | fname | lname | hired      | separated  | job_code | store_id |
+----+-------+-------+------------+------------+----------+----------+
|  1 | a     | a     | 2019-10-10 | 2019-10-10 |        1 |        3 |
+----+-------+-------+------------+------------+----------+----------+

#计算方式
CREATE TABLE t1 (col1 INT, col2 CHAR(5), col3 DATE)
PARTITION BY HASH( YEAR(col3) )
PARTITIONS 4;
#如果插入一条数据对应的col3为'2005-09-15'时,则插入数据的分区计算方法为:
MOD(YEAR('2005-09-01'),4)
= MOD(2005,4)
= 1

五、key表分区

1)key表分区:与哈希表分区类似,只不过哈希表分区依赖于自定义的函数,而key表分区的哈希算法是依赖MySQL本身

2)CREATE TABLE ... PARTITION BY KEY ()创建key表分区,括号里面可以包含0个或者多个字段,所引用的字段必须是主键或者主键的一部分,如果括号里面没有字段,则代表使用主键

CREATE TABLE k1 ( id INT NOT NULL PRIMARY KEY, name VARCHAR(20) )
PARTITION BY KEY()
PARTITIONS 2;

#如果表中没有主键但有唯一键,则使用唯一键,但唯一键字段必须定义为not null,否则报错
mysql> CREATE TABLE k1 ( id INT, name VARCHAR(20), UNIQUE KEY (id) ) PARTITION BY KEY() PARTITIONS 2;
ERROR 1488 (HY000): Field in list of fields for partition function not found in table

CREATE TABLE k1 ( id INT NOT NULL, name VARCHAR(20), UNIQUE KEY (id) ) PARTITION BY KEY()
PARTITIONS 2;

#所引用的字段未必必须是整数类型,其他的类型也可以使用,比如:
CREATE TABLE tm1 ( s1 CHAR(32) PRIMARY KEY )
PARTITION BY KEY(s1)
PARTITIONS 10;

六、子表分区

子表分区,是在表分区的基础上再创建表分区的概念,每个表分区下的子表分区个数必须一致

#Ts表拥有三个范围分区,同时每个分区都各自有两个子分区,所以总共有6个分区
CREATE TABLE ts (id INT, purchased DATE)
PARTITION BY RANGE( YEAR(purchased) )
SUBPARTITION BY HASH( TO_DAYS(purchased) )
SUBPARTITIONS 2
( PARTITION p0 VALUES LESS THAN (1990),
PARTITION p1 VALUES LESS THAN (2000),
PARTITION p2 VALUES LESS THAN MAXVALUE );

[root@db01 part]# ll ts*
-rw-r----- 1 mysql mysql  8596 Oct 10 13:19 ts.frm
-rw-r----- 1 mysql mysql 98304 Oct 10 13:19 ts#P#p0#SP#p0sp0.ibd
-rw-r----- 1 mysql mysql 98304 Oct 10 13:19 ts#P#p0#SP#p0sp1.ibd
-rw-r----- 1 mysql mysql 98304 Oct 10 13:19 ts#P#p1#SP#p1sp0.ibd
-rw-r----- 1 mysql mysql 98304 Oct 10 13:19 ts#P#p1#SP#p1sp1.ibd
-rw-r----- 1 mysql mysql 98304 Oct 10 13:19 ts#P#p2#SP#p2sp0.ibd
-rw-r----- 1 mysql mysql 98304 Oct 10 13:19 ts#P#p2#SP#p2sp1.ibd

#在MySQL5.7版本中,子表分区必须是范围/列表分区+哈希/key子表分区的组合
#子表分区也可以显示的指定子表分区的名字,比如:
CREATE TABLE ts (id INT, purchased DATE)
PARTITION BY RANGE( YEAR(purchased) )
SUBPARTITION BY HASH( TO_DAYS(purchased) )
( PARTITION p0 VALUES LESS THAN (1990)
( SUBPARTITION s0, SUBPARTITION s1 ),
PARTITION p1 VALUES LESS THAN (2000)
( SUBPARTITION s2, SUBPARTITION s3 ),
PARTITION p2 VALUES LESS THAN MAXVALUE
( SUBPARTITION s4, SUBPARTITION s5 ) );

七、不同表分区对Null值处理

不同的表分区对NULL值的处理方式不同

7.1、范围表分区处理null值

对范围表分区来说,如果插入的是NULL值,则将数据放到最小的分区表

CREATE TABLE t1 ( c1 INT, c2 VARCHAR(20) )
PARTITION BY RANGE(c1)
( PARTITION p0 VALUES LESS THAN (0),
PARTITION p1 VALUES LESS THAN (10),
PARTITION p2 VALUES LESS THAN MAXVALUE );

mysql> INSERT INTO t1 VALUES (NULL, 'mothra');
mysql> select * from t1 partition(p0);
+------+--------+
| c1   | c2     |
+------+--------+
| NULL | mothra |
+------+--------+

7.2、list表分区处理null值

对list表分区来说,支持NULL值的唯一情况就是某个分区的允许值中包含NULL

CREATE TABLE ts1 (c1 INT, c2 VARCHAR(20))
PARTITION BY LIST(c1)
(PARTITION p0 VALUES IN (0, 3, 6),
PARTITION p1 VALUES IN (1, 4, 7),
PARTITION p2 VALUES IN (2, 5, 8));

mysql> INSERT INTO ts1 VALUES (9, 'mothra');
ERROR 1504 (HY000): Table has no partition for value 9
mysql> INSERT INTO ts1 VALUES (NULL, 'mothra');
ERROR 1504 (HY000): Table has no partition for value NULL

#解决方法
CREATE TABLE ts2 (c1 INT, c2 VARCHAR(20))
PARTITION BY LIST(c1)
(PARTITION p0 VALUES IN (0, 3, 6),
PARTITION p1 VALUES IN (1, 4, 7),
PARTITION p2 VALUES IN (2, 5, 8),
PARTITION p3 VALUES IN (NULL));

CREATE TABLE ts3 (c1 INT, c2 VARCHAR(20))
PARTITION BY LIST(c1)
(PARTITION p0 VALUES IN (0, 3, 6),
PARTITION p1 VALUES IN (1, 4, 7, NULL),
PARTITION p2 VALUES IN (2, 5, 8));

mysql> INSERT INTO ts2 VALUES (NULL, 'mothra');
Query OK, 1 row affected (0.00 sec)
mysql> INSERT INTO ts3 VALUES (NULL, 'mothra');
Query OK, 1 row affected (0.00 sec)

7.3、哈希表分区和Key表分区处理null值

对哈希表分区和Key表分区来说,NULL值会被当成0值对待

CREATE TABLE th ( c1 INT, c2 VARCHAR(20) )
PARTITION BY HASH(c1)
PARTITIONS 2;

mysql> INSERT INTO th VALUES (NULL, 'mothra'), (0, 'gigan');
mysql> select * from th partition(p0);
+------+--------+
| c1   | c2     |
+------+--------+
| NULL | mothra |
|    0 | gigan  |
+------+--------+

八、MySQL表分区管理

通过alter table命令可以执行增加,删除,重新定义,合并或者拆分表分区的管理动作

8.1、range表分区管理

对范围表分区和列表表分区来说,删除一个表分区命令如下:
CREATE TABLE tr (id INT, name VARCHAR(50), purchased DATE)
PARTITION BY RANGE( YEAR(purchased) )
( PARTITION p0 VALUES LESS THAN (1990), 
PARTITION p1 VALUES LESS THAN (1995), 
PARTITION p2 VALUES LESS THAN (2000), 
PARTITION p3 VALUES LESS THAN (2005), 
PARTITION p4 VALUES LESS THAN (2010), 
PARTITION p5 VALUES LESS THAN (2015) );

mysql> insert into tr values(1, 'abc','1999-12-21');
mysql> select * from tr partition(p2);
+------+------+------------+
| id   | name | purchased  |
+------+------+------------+
|    1 | abc  | 1999-12-21 |
+------+------+------------+

mysql> ALTER TABLE tr DROP PARTITION p2;  #删除表分区
mysql> show create table tr\G
*************************** 1. row ***************************
       Table: tr
Create Table: CREATE TABLE `tr` (
  `id` int(11) DEFAULT NULL,
  `name` varchar(50) DEFAULT NULL,
  `purchased` date DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8
/*!50100 PARTITION BY RANGE ( YEAR(purchased))
(PARTITION p0 VALUES LESS THAN (1990) ENGINE = InnoDB,
 PARTITION p1 VALUES LESS THAN (1995) ENGINE = InnoDB,
 PARTITION p3 VALUES LESS THAN (2005) ENGINE = InnoDB,
 PARTITION p4 VALUES LESS THAN (2010) ENGINE = InnoDB,
 PARTITION p5 VALUES LESS THAN (2015) ENGINE = InnoDB) */

#删除表分区的动作不光会把分区删掉,也会把表分区里原来的数据给删除掉
mysql> SELECT * FROM tr WHERE purchased BETWEEN '1995-01-01' AND '1999-12-31';
Empty set (0.00 sec)

#在原分区上增加一个表分区可以通过alter table … add partition语句来完成
CREATE TABLE members ( 
id INT, fname VARCHAR(25), 
lname VARCHAR(25), 
dob DATE ) PARTITION BY RANGE( YEAR(dob) )
( PARTITION p0 VALUES LESS THAN (1980),
PARTITION p1 VALUES LESS THAN (1990),
PARTITION p2 VALUES LESS THAN (2000) );

mysql> ALTER TABLE members ADD PARTITION (PARTITION p3 VALUES LESS THAN (2010));
mysql> insert into members values(1,'a','b','1978-01-01');
Query OK, 1 row affected (0.01 sec)

#但对范围表分区来说,增加的表分区必须在尾部增加,在头部或者在中间增加都会失败:
mysql> ALTER TABLE members ADD PARTITION ( PARTITION n VALUES LESS THAN (1970));
ERROR 1463 (HY000): VALUES LESS THAN value must be strictly increasing for each partition

#为解决这个问题,可以使用REORGANIZE命令:
ALTER TABLE members REORGANIZE PARTITION p0 INTO
( PARTITION n0 VALUES LESS THAN (1970), PARTITION n1 VALUES LESS THAN (1980) );

mysql> show create table members\G
*************************** 1. row ***************************
       Table: members
Create Table: CREATE TABLE `members` (
  `id` int(11) DEFAULT NULL,
  `fname` varchar(25) DEFAULT NULL,
  `lname` varchar(25) DEFAULT NULL,
  `dob` date DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8
/*!50100 PARTITION BY RANGE ( YEAR(dob))
(PARTITION n0 VALUES LESS THAN (1970) ENGINE = InnoDB,
 PARTITION n1 VALUES LESS THAN (1980) ENGINE = InnoDB,
 PARTITION p1 VALUES LESS THAN (1990) ENGINE = InnoDB,
 PARTITION p2 VALUES LESS THAN (2000) ENGINE = InnoDB,
 PARTITION p3 VALUES LESS THAN (2010) ENGINE = InnoDB) */
 
 
mysql> select * from members;
+------+-------+-------+------------+
| id   | fname | lname | dob        |
+------+-------+-------+------------+
|    1 | a     | b     | 1978-01-01 |
+------+-------+-------+------------+

mysql> select * from members partition(n1);
+------+-------+-------+------------+
| id   | fname | lname | dob        |
+------+-------+-------+------------+
|    1 | a     | b     | 1978-01-01 |
+------+-------+-------+------------+

#范围分区
#当然,也可以通过REORGANIZE命令将之前的多个分区合并成一个或几个分区,但要保持分区值一致:
mysql> ALTER TABLE members REORGANIZE PARTITION n0,n1 INTO ( PARTITION p0 VALUES LESS THAN (1970) );
ERROR 1520 (HY000): Reorganize of range partitions cannot change total ranges except for last partition where it can extend the range
mysql> ALTER TABLE members REORGANIZE PARTITION n0,n1 INTO ( PARTITION p0 VALUES LESS THAN (1985) );
ERROR 1520 (HY000): Reorganize of range partitions cannot change total ranges except for last partition where it can extend the range
mysql> ALTER TABLE members REORGANIZE PARTITION n0,n1 INTO ( PARTITION p0 VALUES LESS THAN (1980) );
Query OK, 0 rows affected (0.04 sec)
Records: 0 Duplicates: 0 Warnings: 0

#更复杂的比如将多个分区重组成多个分区:
ALTER TABLE members REORGANIZE PARTITION p0,p1,p2,p3 INTO ( PARTITION m0 VALUES LESS THAN (1980), PARTITION m1 VALUES LESS THAN (2010));
mysql> ALTER TABLE members REORGANIZE PARTITION p0,p1,p2,p3 INTO ( PARTITION m0 VALUES LESS THAN (1980), PARTITION m1 VALUES LESS THAN (2020));

8.2、list分区管理

#对列表表分区来说,只要新增加的分区对应的值在之前的表分区中没有出现过,就可以通过alter table… add partition来增加
CREATE TABLE tt ( id INT, data INT ) PARTITION BY LIST(data)
( PARTITION p0 VALUES IN (5, 10, 15),
PARTITION p1 VALUES IN (6, 12, 18) );

mysql> ALTER TABLE tt ADD PARTITION (PARTITION p2 VALUES IN (7, 14, 21));

#对列表分区来说,重新组织的分区必须是相邻的分区
mysql> ALTER TABLE tt ADD PARTITION (PARTITION np VALUES IN (4, 8));

mysql> ALTER TABLE tt REORGANIZE PARTITION p1,np INTO ( PARTITION p1 VALUES IN (6, 18), PARTITION np VALUES in (4, 8, 12) );
ERROR 1519 (HY000): When reorganizing a set of partitions they must be in consecutive order

mysql> show create table tt\G
*************************** 1. row ***************************
       Table: tt
Create Table: CREATE TABLE `tt` (
  `id` int(11) DEFAULT NULL,
  `data` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8
/*!50100 PARTITION BY LIST (data)
(PARTITION p0 VALUES IN (5,10,15) ENGINE = InnoDB,
 PARTITION p1 VALUES IN (6,12,18) ENGINE = InnoDB,
 PARTITION p2 VALUES IN (7,14,21) ENGINE = InnoDB,
 PARTITION np VALUES IN (4,8) ENGINE = InnoDB) */

 
mysql> insert into tt values(1,10),(2,5);

mysql> ALTER TABLE tt REORGANIZE PARTITION p0,p1 INTO ( PARTITION p0 VALUES IN (6, 18), PARTITION p1 VALUES in (5,15));
Query OK, 0 rows affected (0.02 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> show create table tt\G
*************************** 1. row ***************************
       Table: tt
Create Table: CREATE TABLE `tt` (
  `id` int(11) DEFAULT NULL,
  `data` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8
/*!50100 PARTITION BY LIST (data)
(PARTITION p0 VALUES IN (6,18) ENGINE = InnoDB,
 PARTITION p1 VALUES IN (5,15) ENGINE = InnoDB,
 PARTITION p2 VALUES IN (7,14,21) ENGINE = InnoDB,
 PARTITION np VALUES IN (4,8) ENGINE = InnoDB) */

#如果表里已有的数据在新重组的分区中没有指定的值,则数据会丢失
mysql> select * from tt;  #(1,10)数据丢失了
+------+------+
| id   | data |
+------+------+
|    2 |    5 |
+------+------+

8.3、哈希表分区和KEY表分区管理

#对哈希表分区和KEY表分区的管理手段与范围和列表表分区完全不同,比如不能删除表分区,但可以通过ALTER TABLE ... COALESCE PARTITION语句合并表分区,其partition后面的数字代表缩减的个数,而不是缩减到的个数

CREATE TABLE clients ( id INT, fname VARCHAR(30), lname VARCHAR(30), signed DATE )
PARTITION BY HASH( MONTH(signed) )
PARTITIONS 12;

mysql> insert into clients values(1,'a','a','2017-01-01'),(2,'a','a','2017-02-01'),(3,'a','a','2017-03-01'),(4,'a','a','2017-04-01');
mysql> select partition_name,table_rows from information_schema.partitions where table_name='clients';
+----------------+------------+
| partition_name | table_rows |
+----------------+------------+
| p0             |          0 |
| p1             |          1 |
| p2             |          1 |
| p3             |          1 |
| p4             |          1 |
| p5             |          0 |
| p6             |          0 |
| p7             |          0 |
| p8             |          0 |
| p9             |          0 |
| p10            |          0 |
| p11            |          0 |
+----------------+------------+

mysql> ALTER TABLE clients COALESCE PARTITION 4;  #缩减的个数
mysql> analyze table clients;
+--------------+---------+----------+----------+
| Table        | Op      | Msg_type | Msg_text |
+--------------+---------+----------+----------+
| part.clients | analyze | status   | OK       |
+--------------+---------+----------+----------+

mysql> select partition_name,table_rows from information_schema.partitions where table_name='clients';
+----------------+------------+
| partition_name | table_rows |
+----------------+------------+
| p0             |          0 |
| p1             |          1 |
| p2             |          1 |
| p3             |          1 |
| p4             |          1 |
| p5             |          0 |
| p6             |          0 |
| p7             |          0 |
+----------------+------------+

#对于哈希表分区和key表分区,如果是增加表分区,则可以使用add partition语句
mysql> ALTER TABLE clients ADD PARTITION PARTITIONS 6;  #添加6个
mysql> analyze table clients;
+--------------+---------+----------+----------+
| Table        | Op      | Msg_type | Msg_text |
+--------------+---------+----------+----------+
| part.clients | analyze | status   | OK       |
+--------------+---------+----------+----------+

mysql> select partition_name,table_rows from information_schema.partitions where table_name='clients';
+----------------+------------+
| partition_name | table_rows |
+----------------+------------+
| p0             |          0 |
| p1             |          1 |
| p2             |          1 |
| p3             |          1 |
| p4             |          1 |
| p5             |          0 |
| p6             |          0 |
| p7             |          0 |
| p8             |          0 |
| p9             |          0 |
| p10            |          0 |
| p11            |          0 |
| p12            |          0 |
| p13            |          0 |
+----------------+------------+

8.4、数据交换exchange命令

对分区表可以通过ALTER TABLE pt EXCHANGE PARTITION p WITH TABLE nt命令将一个分区或者是子分区的数据与普通的表的数据相互交换,其本身的表结构不会变化。交换的分区表和目标表必须结构完全相同,包括字段,类型,索引,存储引擎必须完全一样

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) );

mysql> CREATE TABLE e2 LIKE e;  #创建结构相同的表
mysql> ALTER TABLE e2 REMOVE PARTITIONING; #删除分区

mysql> insert into e(id,fname,lname) values(10,'a','a'),(20,'b','b'),(170,'c','c'),(180,'d','d'),(190,'e','e');

mysql> ALTER TABLE e EXCHANGE PARTITION p0 WITH TABLE e2;  #数据交换,原本p0上的数据不在了

mysql> SELECT PARTITION_NAME, TABLE_ROWS FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_NAME = 'e';
+----------------+------------+
| PARTITION_NAME | TABLE_ROWS |
+----------------+------------+
| p0             |          0 |
| p1             |          0 |
| p2             |          0 |
| p3             |          3 |
+----------------+------------+

mysql> select * from e2;
+----+-------+-------+
| id | fname | lname |
+----+-------+-------+
| 10 | a     | a     |
| 20 | b     | b     |
+----+-------+-------+

mysql> ALTER TABLE e EXCHANGE PARTITION p0 WITH TABLE e2; ##再次执行后数据交换回来
mysql> select * from e2;
Empty set (0.00 sec)
mysql> select * from e;
+-----+-------+-------+
| id  | fname | lname |
+-----+-------+-------+
|  10 | a     | a     |
|  20 | b     | b     |
| 170 | c     | c     |
| 180 | d     | d     |
| 190 | e     | e     |
+-----+-------+-------+

----------------------------------------------------------------------------
#报错:表结构不一致报错lname2 varchar(30)
mysql> create table e3(id int, fname varchar(30),lname2 varchar(30));
mysql> alter table e exchange partition p3 with table e3;
ERROR 1736 (HY000): Tables have different definitions

#报错:字段不一致lname varchar(32)
mysql> create table e3(id int not null, fname varchar(30),lname varchar(32));
Query OK, 0 rows affected (0.04 sec)
mysql> alter table e exchange partition p3 with table e3;
ERROR 1736 (HY000): Tables have different definitions
--------------------------------------------------------------------------------------------

#执行exchange命令时,目标表里不一定是空数据,如果有数据需要保证里面的数据符合表分区的条件,否则只能用WITHOUT VALIDATION来跳过验证环节
mysql> INSERT INTO e2 VALUES (51, "Ellen", "McDonald");
Query OK, 1 row affected (0.08 sec)
mysql> ALTER TABLE e EXCHANGE PARTITION p0 WITH TABLE e2;
ERROR 1707 (HY000): Found row that does not match the partition
mysql> ALTER TABLE e EXCHANGE PARTITION p0 WITH TABLE e2 WITHOUT VALIDATION;
Query OK, 0 rows affected (0.02 sec)

#对子表分区也可以执行exchange命令:
CREATE TABLE es ( id INT NOT NULL, fname VARCHAR(30), lname VARCHAR(30) )
PARTITION BY RANGE (id)
SUBPARTITION BY KEY (lname)
SUBPARTITIONS 2 ( 
PARTITION p0 VALUES LESS THAN (50), 
PARTITION p1 VALUES LESS THAN (100), 
PARTITION p2 VALUES LESS THAN (150), 
PARTITION p3 VALUES LESS THAN (MAXVALUE) );

mysql> CREATE TABLE es2 LIKE es;
mysql> ALTER TABLE es2 REMOVE PARTITIONING;

mysql> ALTER TABLE es EXCHANGE PARTITION p3sp0 WITH TABLE es2;
Query OK, 0 rows affected (0.29 sec)

#当表是有子表分区时,只能exchange一个子表分区,而不能交换整个分区
mysql> ALTER TABLE es EXCHANGE PARTITION p3 WITH TABLE es2;
ERROR 1704 (HY000): Subpartitioned table, use subpartition instead of partition

8.5、其他相关命令

#当需要去除分区碎片时,可以执行rebuild命令,相当于删除数据之后重新插入
ALTER TABLE t1 REBUILD PARTITION p0, p1;

#也可以执行OPTIMIZE命令回收分区中未使用的空间和重新获取统计资料
ALTER TABLE t1 OPTIMIZE PARTITION p0, p1;

#Analyzing partitions命令重新收集分区统计资料
ALTER TABLE t1 ANALYZE PARTITION p3;

#Repairing partitions命令修复异常的分区
ALTER TABLE t1 REPAIR PARTITION p0,p1;

#Checking partitions命令检查分区中数据或者索引数据是否损坏
ALTER TABLE t1 CHECK PARTITION p1;

#ALTER TABLE ... TRUNCATE PARTITION命令用来删除分区中的所有数据
mysql> select * from e partition(p0);
+----+-------+-------+
| id | fname | lname |
+----+-------+-------+
| 10 | a     | a     |
| 20 | b     | b     |
+----+-------+-------+

mysql> alter table e truncate partition p0;
mysql> select * from e partition(p0);
Empty set (0.00 sec)

8.6、表分区信息查看

1)通过show create table命令

mysql> show create table e\G
*************************** 1. row ***************************
       Table: e
Create Table: CREATE TABLE `e` (
  `id` int(11) NOT NULL,
  `fname` varchar(30) DEFAULT NULL,
  `lname` varchar(30) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8
/*!50100 PARTITION BY RANGE (id)
(PARTITION p0 VALUES LESS THAN (50) ENGINE = InnoDB,
 PARTITION p1 VALUES LESS THAN (100) ENGINE = InnoDB,
 PARTITION p2 VALUES LESS THAN (150) ENGINE = InnoDB,
 PARTITION p3 VALUES LESS THAN MAXVALUE ENGINE = InnoDB) */

2)show table status命令

mysql> show table status like 'e';
+------+--------+---------+------------+------+----------------+-------------+-----------------+--------------+-----------+----------------+---------------------+---------------------+------------+-----------------+----------+----------------+---------+
| Name | Engine | Version | Row_format | Rows | Avg_row_length | Data_length | Max_data_length | Index_length | Data_free | Auto_increment | Create_time         | Update_time         | Check_time | Collation       | Checksum | Create_options | Comment |
+------+--------+---------+------------+------+----------------+-------------+-----------------+--------------+-----------+----------------+---------------------+---------------------+------------+-----------------+----------+----------------+---------+
| e    | InnoDB |      10 | Dynamic    |    3 |          21845 |       65536 |               0 |            0 |         0 |           NULL | 2019-10-10 14:37:16 | 2019-10-10 14:37:45 | NULL       | utf8_general_ci |     NULL | partitioned    |         |
+------+--------+---------+------------+------+----------------+-------------+-----------------+--------------+-----------+----------------+---------------------+---------------------+------------+-----------------+----------+----------------+---------+

3)通过information_schema.partitions系统表来查看分区表的具体信息

mysql> select * from information_schema.partitions where table_name='e'\G
*************************** 1. row ***************************
                TABLE_CATALOG: def
                 TABLE_SCHEMA: part
                   TABLE_NAME: e
               PARTITION_NAME: p0
            SUBPARTITION_NAME: NULL
   PARTITION_ORDINAL_POSITION: 1
SUBPARTITION_ORDINAL_POSITION: NULL
             PARTITION_METHOD: RANGE
          SUBPARTITION_METHOD: NULL
         PARTITION_EXPRESSION: id
      SUBPARTITION_EXPRESSION: NULL
        PARTITION_DESCRIPTION: 50
                   TABLE_ROWS: 0
               AVG_ROW_LENGTH: 0
                  DATA_LENGTH: 16384
              MAX_DATA_LENGTH: NULL
                 INDEX_LENGTH: 0
                    DATA_FREE: 0
                  CREATE_TIME: 2019-10-10 14:37:16
                  UPDATE_TIME: 2019-10-10 14:37:45
                   CHECK_TIME: NULL
                     CHECKSUM: NULL
            PARTITION_COMMENT: 
                    NODEGROUP: default
              TABLESPACE_NAME: NULL
*************************** 2. row ***************************
                TABLE_CATALOG: def
                 TABLE_SCHEMA: part
                   TABLE_NAME: e
               PARTITION_NAME: p1
            SUBPARTITION_NAME: NULL
   PARTITION_ORDINAL_POSITION: 2
SUBPARTITION_ORDINAL_POSITION: NULL
             PARTITION_METHOD: RANGE
          SUBPARTITION_METHOD: NULL
         PARTITION_EXPRESSION: id
      SUBPARTITION_EXPRESSION: NULL
        PARTITION_DESCRIPTION: 100
                   TABLE_ROWS: 0
               AVG_ROW_LENGTH: 0
                  DATA_LENGTH: 16384
              MAX_DATA_LENGTH: NULL
                 INDEX_LENGTH: 0
                    DATA_FREE: 0
                  CREATE_TIME: 2019-10-10 14:37:16
                  UPDATE_TIME: 2019-10-10 14:37:45
                   CHECK_TIME: NULL
                     CHECKSUM: NULL
            PARTITION_COMMENT: 
                    NODEGROUP: default
              TABLESPACE_NAME: NULL
*************************** 3. row ***************************
                TABLE_CATALOG: def
                 TABLE_SCHEMA: part
                   TABLE_NAME: e
               PARTITION_NAME: p2
            SUBPARTITION_NAME: NULL
   PARTITION_ORDINAL_POSITION: 3
SUBPARTITION_ORDINAL_POSITION: NULL
             PARTITION_METHOD: RANGE
          SUBPARTITION_METHOD: NULL
         PARTITION_EXPRESSION: id
      SUBPARTITION_EXPRESSION: NULL
        PARTITION_DESCRIPTION: 150
                   TABLE_ROWS: 0
               AVG_ROW_LENGTH: 0
                  DATA_LENGTH: 16384
              MAX_DATA_LENGTH: NULL
                 INDEX_LENGTH: 0
                    DATA_FREE: 0
                  CREATE_TIME: 2019-10-10 14:37:16
                  UPDATE_TIME: 2019-10-10 14:37:45
                   CHECK_TIME: NULL
                     CHECKSUM: NULL
            PARTITION_COMMENT: 
                    NODEGROUP: default
              TABLESPACE_NAME: NULL
*************************** 4. row ***************************
                TABLE_CATALOG: def
                 TABLE_SCHEMA: part
                   TABLE_NAME: e
               PARTITION_NAME: p3
            SUBPARTITION_NAME: NULL
   PARTITION_ORDINAL_POSITION: 4
SUBPARTITION_ORDINAL_POSITION: NULL
             PARTITION_METHOD: RANGE
          SUBPARTITION_METHOD: NULL
         PARTITION_EXPRESSION: id
      SUBPARTITION_EXPRESSION: NULL
        PARTITION_DESCRIPTION: MAXVALUE
                   TABLE_ROWS: 3
               AVG_ROW_LENGTH: 5461
                  DATA_LENGTH: 16384
              MAX_DATA_LENGTH: NULL
                 INDEX_LENGTH: 0
                    DATA_FREE: 0
                  CREATE_TIME: 2019-10-10 14:37:16
                  UPDATE_TIME: 2019-10-10 14:37:45
                   CHECK_TIME: NULL
                     CHECKSUM: NULL
            PARTITION_COMMENT: 
                    NODEGROUP: default
              TABLESPACE_NAME: NULL

九、表分区修剪

#表分区修剪是MySQL优化的一种,其核心就是只扫描需要的分区
CREATE TABLE t1 ( 
fname VARCHAR(50) NOT NULL, 
lname VARCHAR(50) NOT NULL, 
region_code TINYINT UNSIGNED NOT NULL, 
dob DATE NOT NULL ) 
PARTITION BY RANGE( region_code ) ( 
PARTITION p0 VALUES LESS THAN (64), 
PARTITION p1 VALUES LESS THAN (128), 
PARTITION p2 VALUES LESS THAN (192), 
PARTITION p3 VALUES LESS THAN MAXVALUE );

##查看语句执行计划
mysql> explain SELECT fname, lname, region_code, dob FROM t1 WHERE region_code > 125 AND region_code < 130;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | t1    | p1,p2      | ALL  | NULL          | NULL | NULL    | NULL |    1 |   100.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+

#此查询只需要扫描P1和P2两个分区就能得到结果,从而获得额外的性能提升
#不光是select语句可以被使用表分区修剪,update和delete语句也可以使用

#表分区选择和表分区修剪类似,只不过修剪是自动实现的,而表分区选择是现实的指定分区范围
#表分区选择不仅支持select语句,也支持update,insert,delete等语句
CREATE TABLE employees ( 
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, 
fname VARCHAR(25) NOT NULL, 
lname VARCHAR(25) NOT NULL, 
store_id INT NOT NULL, 
department_id INT NOT NULL )
PARTITION BY RANGE(id) ( 
PARTITION p0 VALUES LESS THAN (5),
PARTITION p1 VALUES LESS THAN (10), 
PARTITION p2 VALUES LESS THAN (15), 
PARTITION p3 VALUES LESS THAN MAXVALUE );

mysql> SELECT * FROM employees PARTITION (p1);
mysql> SELECT * FROM employees PARTITION (p0, p2)


SELECT e.id AS 'Employee ID', CONCAT(e.fname, ' ', e.lname) AS Name,
 s.city AS City, d.name AS department
FROM employees AS e
JOIN stores PARTITION (p1) AS s ON e.store_id=s.id
JOIN departments PARTITION (p0) AS d ON e.department_id=d.id
ORDER BY e.lname;

DELETE FROM employees PARTITION (p0, p1) WHERE fname LIKE 'j%';

UPDATE employees PARTITION (p0) SET store_id = 2 WHERE fname = 'Jill';

mysql> INSERT INTO employees PARTITION (p2) VALUES (20, 'Jan', 'Jones', 1, 3);
ERROR 1729 (HY000): Found a row not matching the given partition set
mysql> INSERT INTO employees PARTITION (p3) VALUES (20, 'Jan', 'Jones', 1, 3);
Query OK, 1 row affected (0.07 sec)
posted @ 2019-10-10 11:17  运维人在路上  阅读(711)  评论(0编辑  收藏  举报