mysql表分区详解
1、什么是表分区?
分区是一种表的设计模式,正确的分区可以极大地提升数据库的查询效率,完成更高质量的SQL编程。但是如果错误地使用分区,那么分区可能带来毁灭性的的结果。
分区功能并不是在存储引擎层完成的,常见的存储引擎InnoDB、MyISAM、NDB等都支持分区。但是并不是所有的存储引擎都支持,如CSV、FEDORATED、MERGE等就不支持分区。在使用此分区功能前,应该对选择的存储引擎对分区的支持有所了解。
MySQL数据库在5.1版本及以上时添加了对分区的支持,分区的过程是将一个表或索引分解为多个更小、更可管理的部分。就访问数据库的应用而言,从逻辑上讲,只有一个表或一个索引,但是在物理上这个表或索引可能由数十个物理分区组成。每个分区都是独立的对象,可以独自处理,也可以作为一个更大对象的一部分进行处理。
MySQL数据库支持的分区类型为水平分区(指将同一个表中不同行的记录分配到不同的物理文件中),并不支持垂直分区(指将同一表中不同列的记录分配到不同的物理文件中)。此外,MySQL数据库的分区是局部分区索引,一个分区中既存放了数据又存放了索引。而全局分区是指,数据存放在各个分区中,但是所有数据的索引放在一个对象中。目前,MySQL数据库还不支持全局分区。
2、表分区有什么好处?
分区的好处是:
- 可以让单表存储更多的数据
- 分区表的数据更容易维护,可以通过清楚整个分区批量删除大量数据,也可以增加新的分区来支持新插入的数据。另外,还可以对一个独立分区进行优化、检查、修复等操作
- 部分查询能够从查询条件确定只落在少数分区上,速度会很快(查询条件尽量扫描少的分区)
- 分区表的数据还可以分布在不同的物理设备上,从而高效利用多个硬件设备
- 可以使用分区表来避免某些特殊瓶颈,例如InnoDB单个索引的互斥访问、ext3文件系统的inode锁竞争
- 可以备份和恢复单个分区
分区的限制和缺点:
- 一个表最多只能有1024个分区
- 如果分区字段中有主键或者唯一索引的列,那么所有主键列和唯一索引列都必须包含进来
- 分区表无法使用外键约束
- NULL值会使分区过滤无效
- 所有分区必须使用相同的存储引擎
3、表分区与分表的区别?
分表:指的是通过一定规则,将一张表分解成多张不同的表。比如将用户订单记录根据时间成多个表。 分表与分区的区别在于:分区从逻辑上来讲只有一张表,而分表则是将一张表分解成多张表。
4、如何判断当前MySQL是否支持分区?
mysql> show variables like '%partition%'; +-------------------+-------+ | Variable_name | Value | +-------------------+-------+ | have_partitioning | YES | +-------------------+-------+ row in set (0.00 sec)
have_partintioning 的值为YES,表示支持分区。
5:MySQL分区类型
注意:如果分区字段中有主键或者唯一索引的列,那么所有主键列和唯一索引列都必须包含进来(即分区的字段也必须是主键或者唯一索引)
1)RANGE分区
我们介绍的第一种分区类型是RANGE分区,也是最常用的一种分区类型,基于属于一个给定连续区间的列值,把多行分配给分区。
这些区间要连续且不能相互重叠,使用VALUES LESS THAN操作符来进行定义。不使用COLUMNS
关键字时RANGE
括号内必须为整数字段名或返回确定整数的函数。
下面创建一个以id列为区间分区表,当id小于10时,数据插入p0分区;当id大于等于10小于20时,数据插入p1分区。
create table t(
idint
)engine=innodb
partition by range(id)(
partition p0 values less than(10),
partition p1 values less than(20)
);
查看表在磁盘上的物理文件,启用分区之后,表不再由一个ibd文件组成了。而是由建立分区时的各个分区ibd文件组成。
如何查看 t 表的分区信息:
select * from information_schema.partitions where table_schema=database() and table_name='t'
注意:对于表t,由于我们定义了分区,因此对于插入的值应该严格遵守分区的定义,当插入一个不在分区中定义的值时,MySQL数据库会抛出一个异常。如下所示:
mysql>insert into t select 30; ERROR1526(HY000):Table has no partition for value 30
对于上述问题,我们可以对分区添加一个MAXVALUE值的分区,MAXVALUE可以理解为正无穷,因此所有大于等于20且小于MAXVALUE的值被放入p2分区。
alter table t add partition(partition p2 values less than maxvalue);
RANGE分区主要用于日期列的分区,例如对于销售类的表,可以根据年来分区存放销售记录,如下面的分区表sales。
create table sales(
money int unsigned notnull,
date datetime
)engine=innodb
partition by range(year(date))(
partition p2014 values less than(2015),
partition p2015 values less than(2016),
partition p2016 values less than(2017)
);
这样创建的好处就是便于对sales这张表的管理。如果我们要删除2015年的数据,不需要执行delete from sales where date>=’2015-01-01′ and dater<= ‘2016-01-01’,只需要删除2015年所在的分区即可。
alter table sales drop partition p2015;
这样创建的另一个好处就是可以加快某些查询操作,如果我们只需要查询2014年整年的销售额,可以这样:
explain partitions select * from sales where date BETWEEN '2014-01-01' and '2014-12-31'
通过explain partitions命令我们可以发现,在上述语句中,SQL优化只需要去搜索p2014这个分区,而不会去搜索所有的分区,称为分区修剪(partition pruning),故查询的速度得到了大幅度的提升。需要注意的是,如果执行下列语句,结果是一样的,但是优化器的选择可能又会不同了。partitions:p2014,p2015
explain partitions select * from sales where date BETWEEN '2014-01-01' and '2015-01-01'
在进行分区时,如果出现“This partition function is not allowed”的错误提示,则你可能使用了非支持函数。MySQL 5.6支持的partition函数:http://dev.mysql.com/doc/refman/5.6/en/partitioning-limitations-functions.html
2)LIST分区
LIST分区和RANGE分区类似,区别在于LIST分区是基于列值匹配一个离散值集合中的某个值来进行选择,而非连续的。
LIST分区通过使用“PARTITION BY LIST(expr)”来实现,其中“expr” 是某列值或一个基于某个列值、并返回一个整数值的表达式,然后通过“VALUES IN (value_list)”的方式来定义每个分区,其中“value_list”是一个通过逗号分隔的整数列表。
假定有20个音像店,分布在4个有经销权的地区,如下表所示:
不同于RANGE分区中定义的VALUES LESS THAN语句,LIST分区使用VALUES IN,因为每个分区的值是离散的,因此只能定义值。按照属于同一个地区商店的行保存在同一个分区中的方式来分割表,可以使用下面的“CREATE TABLE”语句:
CREATE TABLE employees (
id INT NOT NULL,
name VARCHAR(30),
hired DATE NOT NULL DEFAULT '1970-01-01',
separated DATE NOT NULL DEFAULT '9999-12-31',
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)
);
这使得在表中增加或删除指定地区的雇员记录变得容易起来。例如,假定西区的所有音像店都卖给了其他公司。那么与在西区音像店工作雇员相关的所有记录(行)可以使用“ALTER TABLE employees DROP PARTITION pWest;”来进行删除,它与具有同样作用的DELETE (删除)查询“DELETE query DELETE FROM employees WHERE store_id IN (4,12,13,14,18);”比起来,要有效得多。
【要点】如果试图插入列值(或分区表达式的返回值)不在分区值列表中的一行时,那么“INSERT”查询将失败并报错。例如,假定LIST分区的采用上面的方案,下面的查询将失败:
INSERT INTO employees VALUES(224,'Linus','2015-05-01','2015-10-12',42,21);
这是因为“store_id”列值21不能在用于定义分区pNorth, pEast, pWest,或pCentral的值列表中找到。要重点注意的是,LIST分区没有类似如“VALUES LESS THAN MAXVALUE”这样的包含其他值在内的定义。将要匹配的任何值都必须在值列表中找到。
3)HASH分区
HASH分区的目的是将数据均匀地分布到预先定义的各个分区中,保证各分区的数据量大致都是一样的。在RANGE和LIST分区中,必须明确指定一个给定的列值或列值集合应该保存在哪个分区中;而在HASH分区中,MySQL自动完成这些工作,用户所要做的只是基于将要进行哈希分区的列值指定一个列值或表达式,以及指定被分区的表将要被分隔成的分区数量。
要使用HASH分区来分割一个表,要在CREATE TABLE 语句上添加一个“PARTITION BY HASH (expr)”子句,其中“expr”是一个返回一个整数的表达式。它可以仅仅是字段类型为MySQL 整型的一列的名字。此外,你很可能需要在后面再添加一个“PARTITIONS num”子句,其中num是一个非负的整数,它表示表将要被分割成分区的数量,如果没有包括一个PARTITIONS子句,那么分区的数量将默认为1。
CREATE TABLE employees_h (
id INT NOT NULL,
lname VARCHAR(30),
store_id INT
)
PARTITION BY HASH(store_id)
PARTITIONS 4;
4)KEY分区
KEY分区和HASH分区相似,不同之处在于HASH分区使用用户定义的函数进行分区,支持字符串HASH分区,KEY分区使用MySQL数据库提供的函数进行分区,这些函数基于与PASSWORD()一样的运算法则。
5)COLUMNS
在前面说了RANGE、LIST、HASH和KEY这四种分区中,分区的条件是:数据必须为整形(interger),如果不是整形,那应该需要通过函数将其转化为整形,如YEAR(),TO_DAYS(),MONTH()等函数。MySQL5.5版本开始支持COLUMNS分区,可视为RANGE分区和LIST分区的一种进化。COLUMNS分区可以直接使用非整形的数据进行分区,分区根据类型直接比较而得,不需要转化为整形。此外,RANGE COLUMNS分区可以对多个列的值进行分区。
COLUMNS分区支持以下的数据类型:
- 所有的整形类型,如INT、SMALLINT、TINYINT和BIGINT。而FLOAT和DECIMAL则不予支持。
- 日期类型,如DATE何DATETIME。其余的日期类型不予支持。
- 字符串类型,如CHAR、VARCHAR、BINARY和VARBINARY。而BLOB和TEXT类型不予支持。
对于日期类型的分区,我们不再需要YEAR()和TO_DATS()函数了,而直接可以使用COLUMNS,如:
CREATE TABLE `t_c` (
`key` varchar(50),
`value` varchar(50),
`create_time` datetime
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
PARTITION BY RANGE COLUMNS (create_time) (
PARTITION p0 VALUES LESS THAN ('2017-01-01 00:00:00'),
PARTITION p1 VALUES LESS THAN ('2017-03-01 00:00:00')
);
同样可以使用字符串分区。
CREATE TABLE`monitor_2`(
`key`varchar(15),
`value`varchar(50),
`create_time`datetime,
`city`VARCHAR(15)
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
PARTITION BY LIST COLUMNS (city)(
PARTITION p0 VALUES IN('shanghai','beijing','shenzhen'),
PARTITION p1 VALUES IN('hubei','henan','hunan')
);
对比RANGE分区和LIST分区,Columns分区的亮点除了支持数据类型增加之外,另外一大亮点是Columns分区还支持多列分区。如:
CREATE TABLE `monitor_3` (
`key` varchar(15),
`value` varchar(50),
`create_time` datetime,
`test` VARCHAR(1)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
PARTITION BY RANGE COLUMNS(create_time,test) (
PARTITION p0 VALUES LESS THAN ('2017-01-01 00:00:00','yes'),
PARTITION p1 VALUES LESS THAN ('2017-03-01 00:00:00','no'),
PARTITION p2 VALUES LESS THAN (MAXVALUE,MAXVALUE)
);
6)分区中的NULL值
MySQL数据库允许对NULL值做分区,但是处理的方法与其他数据库可能完全不同。MySQL数据库的分区总是视NULL值小于任何的一个非NULL值,这和MySQL数据库中处理NULL值的ORDER BY操作是一样的。因此对于不同的分区类型,MySQL数据库对于NULL值的处理也是各不相同。
1)对于RANGE分区,如果向分区列插入了NULL值,则MySQL数据库会将该值放入最左边的分区(第一个分区)。
2)对于LIST分区,如果向分区列插入了NULL值,则必须显示地指出哪个分区放入NULL值,否则会报错。
3)对于HASH和KEY分区,对于NULL值的处理方法和RANGE分区、LIST分区不一样。任何分区函数都会将含有NULL值的记录返回为0。
1:创建表分区: ALTER TABLE my_user PARTITION by RANGE COLUMNS (age) ( PARTITION p30 VALUES LESS THAN (30), PARTITION p40 VALUES LESS THAN (40), PARTITION p50 VALUES LESS THAN (50), PARTITION p60 VALUES LESS THAN (100) -- 剩下的放在一个分区中,当需要对这部分进行再次分区的时候,需要先删除该分区,然后再添加多个分区 ); 2:创建分区后增加分区: alter table my_user add partition (partition p3 values less than (4000)); -- range 分区 alter table my_user add partition (partition p3 values in (40)); -- lists分区 3:删除表分区(注意这种方式删除分区,会删除数据): alter table my_user drop partition p30; 4:删除表的所有分区(不会丢失数据):
Alter table my_user remove partitioning; 5:查看创建分区表的create语句 show create table my_user 6:可以查看表是不是分区表(可以根据Create_options查看) show table status 7:explain partitions select 语句: 通过此语句来显示扫描哪些分区,及他们是如何使用的 8:重新定义range分区表: ----不会丢失数据 Alter table my_user partition by range(salary)( partition p1 values less than (2000), partition p2 values less than (4000) ); 9:重新定义hash分区表: ----不会丢失数据 Alter table my_user partition by hash(salary) partitions 7; 10:合并分区: Merge分区:把2个分区合并为一个----不会丢失数据。 alter table my_user reorganize partition p1,p3 into (partition p1 values less than (1000)); 11:分解分区: reorganize partition 关键字可以对表的部分分区或全部分区进行修改,并且不会丢失数据。分解前后分区的整体范围应该一致。 alter table my_user reorganize partition p1 into( partition p1 values less than (100), partition p3 values less than (1000) );