1、对表分区的原因
数据库数据越来越大,导致单个表中数据太多。以至于增删改查询速度变慢,而且由于表的锁机制导致应用操作也受到严重影响,出现了数据库性能瓶颈。
2、表分区
表分区就是将一个表的数据按照一定的规则水平划分为不同的逻辑块,并分别进行物理存储,这个规则就叫做分区函数,可以有不同的分区规则,通过”show plugins”语句可以查看当前MySQL是否支持表分区功能,mysql5.7社区版默认开启了表分区功能。
3、进行表分区的优势
(1)可以允许在一个表里存储更多的数据,突破磁盘限制或者文件系统限制
(2)对于从表里将过期或历史的数据移除在表分区很容易实现,只要将对应的分区移除即可
(3)对某些查询和修改语句来说,可以自动将数据范围缩小到一个或几个表分区上,优化语句执行效率。而且可以通过显示指定表分区来执行语句
3、表分区的类型
(1)RANGE表分区:范围表分区,按照一定的范围值来确定每个分区包含的数据
(2)LIST表分区:列表表分区,按照一个确定的值来确定每个分区包含的数据
(3)HASH表分区:哈希表分区,按照一个自定义的函数返回值来确定每个分区包含的数据
(4)KEY表分区 :key表分区,与哈希表分区类似,只是用MySQL自己的HASH函数来确定每个分区包含的数据
1、RANGE表分区的使用
RANG表分区(范围表分区),按照一定的范围值来确定每个分区包含的数据,分区函数使用的字段必须只能是整数类型,分区的定义范围必须是连续的,且不能有重叠部分,通过使用VALUES LESS THAN来定义分区范围,表分区的范围定义是从小到大定义的。
mysql> create table range_test(id int not null,
name1 varchar(30),name2 varchar(30),
hired date not null default '1970-01-01',
separated date not null default '2999-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(12),
partition p2 values less than(18),partition p3 values less than(24));
# 插入数据
mysql> insert into range_test values(1,'dayi','dayi123',now(),now(),2,6),(2,'dy','liu',now(),now(),5,10);
# 查询时可以指定具体的分区表查询
mysql> select * from range_test partition (p1);
+----+-------+---------+------------+------------+----------+----------+
| id | name1 | name2 | hired | separated | job_code | store_id |
+----+-------+---------+------------+------------+----------+----------+
| 1 | dayi | dayi123 | 2018-09-30 | 2018-09-30 | 2 | 6 |
| 2 | dy | liu | 2018-09-30 | 2018-09-30 | 5 | 10 |
+----+-------+---------+------------+------------+----------+----------+
# 由于分区表p0中没有数据所以查不到
mysql> select * from range_test partition (p0);
Empty set (0.00 sec)
# 查看数据文件时每个分区表都会生成一个独立的数据文件
]# ls /data/mysql/data/test/range_test*
range_test.frm range_test#P#p0.ibd range_test#P#p1.ibd range_test#P#p2.ibd range_test#P#p3.ibd
当插入的数据的分区字段的值不在分区表指定的范围时会报错。这是需要修改下表的定义,可以使用MAXVALUE关键词表示可能的最大值。
对timestamp字段类型可以使用的表达式目前仅有unix_timestamp(作用是将时间转化为时间戳),其他的表达式都不允许。对于date及datetime类型的数据可以将”year”提取出来。
# 依据timestamp类型字段表分区时将该字段转化成时间戳
mysql> CREATE TABLE range_test02 ( id INT NOT NULL,name VARCHAR(20) NOT NULL,update_date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP )PARTITION BY RANGE ( UNIX_TIMESTAMP(update_date) )
( PARTITION p0 VALUES LESS THAN ( UNIX_TIMESTAMP('2017-01-01 00:00:00') ),
PARTITION p1 VALUES LESS THAN ( UNIX_TIMESTAMP('2018-01-01 00:00:00') ),
PARTITION p2 VALUES LESS THAN ( UNIX_TIMESTAMP('2019-01-01 00:00:00') ),
PARTITION p3 VALUES LESS THAN ( UNIX_TIMESTAMP('2020-10-01 00:00:00') ),
PARTITION p9 VALUES LESS THAN (MAXVALUE) );
mysql> insert into range_test02 values(1,'dayi123',now()),(2,'testname','2030-01-02 12:00:00');
Query OK, 2 rows affected (2.34 sec)
# 使用date类型的数据表分区时可将”year”提取出来利用”year”进行表分区
mysql> create table range_test04(tstamp date) partition by range(year(tstamp))(partition p0 values less than(2018));
2、list表分区的使用
使用list表分区时,PARTITION BY LIST(expr)分区函数表达式必须返回整数,取值范围通过VALUES IN (value_list)定义。
# 创建基于list表分区的表
mysql> create table list_test01(id int not null,
name 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 p_north values in (1,4,7),
partition p_east values in (2,5,8),
partition p_west values in (3,6,9));
基于list表分区的表创建注意项:
(1)对List表分区来说,没有MAXVALUE特殊值,所有的可能取值都需要再VALUES IN中包含,如果有定义的取值则会报错.
(2)当有主键或者唯一键存在的情况下,分区函数字段需要包含在主键或唯一键中。
3、hash表分区的使用
哈希表分区是按照一个自定义的函数返回值来确定每个分区包含的数据,这个自定义函数也可以仅仅是一个字段名字
通过PARTITION BY HASH (expr)子句来表达哈希表分区,其中的expr表达式必须返回一个整数,基于分区个数的取模(%)运算。根据余数插入到指定的分区,对哈希表分区来说只需要通过” PARTITIONS”定义分区的个数,其他的事情由内部完成;如果没有写明PARTITIONS字段,则默认为1。
# 创建基于hash表分区的表
mysql> create table hash_test01(id int not null,name varchar(30),
hired date not null default '1970-01-01',
separated date not null default '2999-12-31',
job_code int,store_id INT) partition by hash(store_id)
partitions 5;
# 查看创建的hash表分区的分区表
mysql> select table_name,partition_name from information_schema.partitions where table_name='hash_test01';
+-------------+----------------+
| table_name | partition_name |
+-------------+----------------+
| hash_test01 | p0 |
| hash_test01 | p1 |
| hash_test01 | p2 |
| hash_test01 | p3 |
| hash_test01 | p4 |
+-------------+----------------+
4、key表分区的使用
key表分区与哈希表分区类似,只不过哈希表分区依赖于自定义的函数,而key表分区的哈希算法是依赖MySQL本身。
Key表分区通过CREATE TABLE ... PARTITION BY KEY ()语句创建,括号里面可以包含0个或者多个字段,所引用的字段必须是主键或者主键的一部分,如果括号里面没有字段,则代表使用主键,如果表中没有主键但有唯一键,则使用唯一键,但唯一键字段必须定义为not null,否则报错。Key表分区所引用的字段未必是整数类型,其他的类型也可以使用。
# 没有指定字段,则使用主键
mysql> CREATE TABLE key_test01 ( id INT NOT NULL PRIMARY KEY, name VARCHAR(20) ) PARTITION BY KEY() PARTITIONS 2;
# 指定字符串作为key表分区的字段
mysql> create table key_test02(id int,tname char(30) primary key) partition by key(tname) partitions 10;
5、创建子表分区
子表分区,是在表分区的基础上再创建表分区的概念,每个表分区下的子表分区个数必须一致,在MySQL5.7版本中,子表分区必须是范围/列表分区+哈希/key子表分区的组合。
# 创建字表分区
mysql> create table test_zb01(id int,purchased date)
partition by range(year(purchased))
subpartition by hash(to_days(purchased))
subpartitions 2
(partition p0 values less than(2000),
partition p1 values less than(2020),
partition p2 values less than maxvalue);
# 查看字表的表名
mysql> select table_name,partition_name,SUBPARTITION_NAME from information_schema.partitions where table_name='test_zb01';
+------------+----------------+-------------------+
| table_name | partition_name | SUBPARTITION_NAME |
+------------+----------------+-------------------+
| test_zb01 | p0 | p0sp0 |
| test_zb01 | p0 | p0sp1 |
| test_zb01 | p1 | p1sp0 |
| test_zb01 | p1 | p1sp1 |
| test_zb01 | p2 | p2sp0 |
| test_zb01 | p2 | p2sp1 |
+------------+----------------+-------------------+
字表在不指定表名时,表名时系统默认分配的。子表分区也可以显示的指定子表分区的名字。
# 创建字表时指定子分区表名
mysql> create table test_zb02(id int,purchased date)
partition by range(year(purchased)) subpartition by hash(to_days(purchased)) subpartitions 2
(partition p0 values less than(2000)
(subpartition s1,subpartition s2),
partition p1 values less than(2020)
(subpartition s3,subpartition s4),
partition p2 values less than maxvalue
(subpartition s5,subpartition s6));
4、进行表分区时需要注意的事项
当表中含有主键或唯一键时,每个被用作分区函数的字段必须是表中唯一键和主键的全部或一部分,否则就无法创建分区表。
# 由于唯一键和主键没有相同的字段,所以无法创建表分区
mysql> create table pattern_test01(
-> id int primary key,
-> name varchar(12) unique,
-> age int) partition by range (id)
-> (partition p0 values less than(10),
-> partition p1 values less than(20));
ERROR 1503 (HY000): A UNIQUE INDEX must include all columns in the table's partitioning function
# 不使用唯一键可创建成功
mysql> create table pattern_test02(
-> id int primary key,
-> name varchar(12),
-> age int) partition by range (id)
-> (partition p0 values less than(10),
-> partition p1 values less than(20));
Query OK, 0 rows affected (0.16 sec)
# 同时将主键设置为唯一键
mysql> create table pattern_test03(
-> id int,name varchar(12),age int,
-> primary key (id,name),
-> unique key (id)) partition by range (id)
-> (partition p0 values less than(10),
-> partition p1 values less than(20));
Query OK, 0 rows affected (0.14 sec)
表分区创建完成后可以通过alter table命令可以执行增加,删除,重新定义,合并或者拆分表分区的管理动作。对不同类型的表分区管理方法也有不同。
1、表分区的删除
对范围表分区和列表表分区来说,删除一个表分区时使用命令” ALTER TABLE table_name DROP PARTITION partition_name”删除;删除表分区的动作不光会把分区删掉,也会把表分区里原来的数据给删除掉。
# 查看创建表的语句
mysql> show create table range_test;
+------------+----------------------------------------------------+
|Tabl|CreateTable
+------------+-----------------------------------------+
| range_test | CREATE TABLE `range_test` (
`id` int(11) NOT NULL,
`name1` varchar(30) DEFAULT NULL,
`name2` varchar(30) DEFAULT NULL,
`hired` date NOT NULL DEFAULT '1970-01-01',
`separated` date NOT NULL DEFAULT '2999-12-31',
`job_code` int(11) NOT NULL,
`store_id` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1
/*!50100 PARTITION BY RANGE (store_id)
(PARTITION p0 VALUES LESS THAN (6) ENGINE = InnoDB,
PARTITION p1 VALUES LESS THAN (12) ENGINE = InnoDB,
PARTITION p2 VALUES LESS THAN (18) ENGINE = InnoDB,
PARTITION p3 VALUES LESS THAN (24) ENGINE = InnoDB) */ |
+------------+----------------------------------------------------------+
# 删除分区表p3
mysql> alter table range_test drop partition p3;
2、增加表分区
(1)范围表分区(range)上增加及重新组织表分区
在原分区上增加一个表分区可以通过alter table … add partition语句来完成。对范围表分区来说,增加的表分区必须在尾部增加,在头部或者在中间增加都会失败,对此可以通过REORGANIZ增加表分区及重组表分区。
# 增加表分区
mysql> alter table range_test add partition (partition p3 values less than maxvalue);
# 使用REORGANIZ命令创建表分区,相当于将p0分成两个表分区
mysql> alter table range_test REORGANIZE PARTITION p0 into (partition n0 values less than (3),partition n1 values less than (6));
#使用REORGANIZ命令也可将拆分的表分区合并
mysql> alter table range_test REORGANIZE PARTITION n0,n1 into (partition p0 values less than(6));
(2)列表表分区(list)上增加及重新组织表分区
对列表表分区来说,只要新增加的分区对应的值在之前的表分区中没有出现过,就可以通过alter table… add partition来增加。也可以通过REORGANIZE命令将之前的多个分区合并成一个或几个分区,但要保持分区值一致;对列表分区重组时,重新组织的分区必须是相邻的分区。
# 增加表分区
mysql> alter table list_test01 add partition (partition pCentral values in(10,11,12));
# 重新组织表分区
mysql> alter table list_test01 REORGANIZE partition p_north,p_east,p_west into (partition p0 values in (1,2,3),partition p1 values in (4,5,6),partition p2 values in (7,8,9));
如果表里已有的数据在新重组的分区中没有指定的值,则数据会丢失
3、哈希表分区和key表分区的管理
对哈希表分区和KEY表分区的管理手段与范围和列表表分区完全不同,比如不能删除表分区,但可以通过ALTER TABLE ... COALESCE PARTITION语句合并表分区,其partition后面的数字代表缩减的个数,而不是缩减到的个数;如果是增加表分区,则可以使用add partition语句。
# 将表hash_test01的表分区缩减到三个
mysql> alter table hash_test01 COALESCE PARTITION 3;
# 对表has_test01再增加10个表分区
mysql> alter table hash_test01 ADD PARTITION PARTITIONS 10;
1、创建range和list表分区时,分区函数可以包含多个字段
分区多字段函数所涉及的字段类型可以包括TINYINT、SMALLINT、MEDIUMINT、INT (INTEGER)、 BIGINT、DATE、DATETIME、CHAR、VARCHAR、BINARY、 VARBINARY。
# 创建多字段表分区的rang分区表
mysql> create table columns_test(id int not null,name varchar(30),
tname varchar(30),store_id int) partition by range columns(id,tname,store_id)
(partition p0 values less than (100,'dayi123',3),
partition p1 values less than (200,'dy',6),
partition p2 values less than (MAXVALUE,MAXVALUE,MAXVALUE));
2、对null值得处理
(1)在范围表分区中,如果插入的是NULL值,则将数据放到最小的分区表里
(2)在list表分区中,支持NULL值的唯一情况就是某个分区的允许值中包含NULL
(3)对哈希表分区和Key表分区来说,NULL值会被当成0值对待
3、表分区数据的交换
对分区表可以通过ALTER TABLE pt EXCHANGE PARTITION p WITH TABLE nt命令将一个分区或者是子分区的数据与普通的表的数据相互交换,其本身的表结构不会变化;交换的分区表和目标表必须结构完全相同,包括字段,类型,索引,存储引擎必须完全一样。
# 创建用于数据交换分区的表
mysql> create table ext_test01 like range_test;
# 数据交换
mysql> alter table ext_test01 remove partitioning;
mysql> alter table range_test exchange partition p1 with table ext_test01;
# 查看数据交换后各表的数据
mysql> select * from ext_test01;
+----+-------+---------+------------+------------+----------+----------+
| id | name1 | name2 | hired | separated | job_code | store_id |
+----+-------+---------+------------+------------+----------+----------+
| 1 | dayi | dayi123 | 2018-09-30 | 2018-09-30 | 2 | 6 |
| 2 | dy | liu | 2018-09-30 | 2018-09-30 | 5 | 10 |
+----+-------+---------+------------+------------+----------+----------+
mysql> select * from range_test partition (p1);
Empty set (0.00 sec)
4、表分区的其他管理命令
(1)使用rebuild命令除去分区碎片
mysql> alter table range_test rebuild partition p0,p1;
(2)OPTIMIZE命令可以回收分区中未使用的空间和重新获取统计资料
mysql> alter table range_test optimize partition p0,p1,p2;
(3)Analyzing partitions命令用于重新收集分区统计资料
mysql> alter table range_test analyze partition p3;
(4)Repairing partitions命令用于修复异常的分区
mysql> alter table range_test repair partition p0,p1;
(5)Checking partitions命令检查分区中数据或者索引数据是否损坏
mysql> alter table range_test check partition p0;
(6)ALTER TABLE ... TRUNCATE PARTITION命令用来删除分区中的所有数据
mysql> alter table range_test truncate partition p3;
5、表分区的修剪和选择
表分区修剪是MySQL优化的一种,其核心就是只扫描需要的分区。
表分区选择和表分区修剪类似,只不过修剪是自动实现的,而表分区选择是现实的指定分区范围。