分区优势
1、冷热分离:表非常大且只在表的最后部分有热点数据,冷数据根据分区规则自动归档。
2、定期淘汰历史数据:按时间写入,历史数据可淘汰,可快速删除,空间可快速回收。
3、优化查询:在where字句中包含分区列时,分区可以大大提高查询效率,减少缓存开销、减少IO开销。
4、统计性能提升:在涉及sum()和count()这类聚合函数的查询时,可以在每个分区上面并行处理,最终只需要汇总所有分区得到的结果。
分区类型
目前MySQL支持范围分区(RANGE),列表分区(LIST),哈希分区(HASH)以及KEY分区四种。下面我们逐一介绍每种分区:
RANGE分区
基于属于一个给定连续区间的列值,把多行分配给分区。最常见的是基于时间字段. 基于分区的列最好是整型,如果日期型的可以使用函数转换为整型。
CREATE TABLE `kx_storage_log_part1` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`brandid` int(11) NOT NULL DEFAULT '1' COMMENT '品牌商id',
`in_store` varchar(32) NOT NULL DEFAULT '' COMMENT '入库方',
`out_store` varchar(32) NOT NULL DEFAULT '' COMMENT '出库方',
`price` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '出入库单价',
`created_at` int(11) NOT NULL DEFAULT '0' COMMENT '创建时间',
PRIMARY KEY (`id`,`created_at`)
) ENGINE=InnoDB AUTO_INCREMENT=15925508 DEFAULT CHARSET=utf8 COMMENT='库存日志表'
p11是一个默认分区,所有大于1522512000的记录都会在这个分区。MAXVALUE是一个无穷大的值。p11是一个可选分区。如果在定义表的没有指定的这个分区,当我们插入大于1522512000的数据的时候,会收到一个错误。
我们在执行查询的时候,必须带上分区字段。这样可以使用分区剪裁功能
mysql> insert into kx_storage_log_part1 select * from kx_storage_log;
mysql> select count(1) from kx_storage_log where created_at <= 1494657605;
+----------+
| count(1) |
+----------+
| 1082 |
+----------+
1 row in set (54.11 sec)
mysql> select count(1) from kx_storage_log_part1 where created_at <= 1494657605;
+----------+
| count(1) |
+----------+
| 1075 |
+----------+
1 row in set (0.27 sec)
mysql> explain select count(1) from kx_storage_log_part1 where created_at <= 1496246400;
+----+-------------+----------------------+------------+-------+---------------+------+---------+------+--------+----------+--------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+----------------------+------------+-------+---------------+------+---------+------+--------+----------+--------------------------+
| 1 | SIMPLE | kx_storage_log_part1 | p0,p1,p2 | index | NULL | type | 1 | NULL | 332999 | 33.33 | Using where; Using index |
+----+-------------+----------------------+------------+-------+---------------+------+---------+------+--------+----------+--------------------------+
在5.7版本之前,对于DATA和DATETIME类型的列,如果要实现分区裁剪,只能使
YEAR() 和TO_DAYS()函数,在5.7版本中,又新增了TO_SECONDS()函数
mysql> CREATE TABLE part_date3
-> ( c1 int default NULL,
-> c2 varchar(30) default NULL,
-> c3 date default NULL) engine=myisam
-> partition by range (to_days(c3))
-> (PARTITION p0 VALUES LESS THAN (to_days('1995-01-01')),
-> PARTITION p1 VALUES LESS THAN (to_days('1996-01-01')) ,
-> PARTITION p2 VALUES LESS THAN (to_days('1997-01-01')) ,
-> PARTITION p3 VALUES LESS THAN (to_days('1998-01-01')) ,
-> PARTITION p4 VALUES LESS THAN (to_days('1999-01-01')) ,
-> PARTITION p5 VALUES LESS THAN (to_days('2000-01-01')) ,
-> PARTITION p6 VALUES LESS THAN (to_days('2001-01-01')) ,
-> PARTITION p7 VALUES LESS THAN (to_days('2002-01-01')) ,
-> PARTITION p8 VALUES LESS THAN (to_days('2003-01-01')) ,
-> PARTITION p9 VALUES LESS THAN (to_days('2004-01-01')) ,
-> PARTITION p10 VALUES LESS THAN (to_days('2010-01-01')),
-> PARTITION p11 VALUES LESS THAN MAXVALUE );
Query OK, 0 rows affected (0.00 sec)
LIST分区
LIST分区和RANGE分区类似,区别在于LIST是枚举值列表的集合,RANGE是连续的区间值的集合。二者在语法方面非常的相似。同样建议LIST分区列是非null列,否则插入null值如果枚举列表里面不存在null值会插入失败,这点和其它的分区不一样,RANGE分区会将其作为最小分区值存储,HASH\KEY分为会将其转换成0存储,主要LIST分区只支持整形,非整形字段需要通过函数转换成整形.
create table t_list(
a int(11),
b int(11)
)(
partition by list (b)
partition p0 values in (1,3,5,7,9),
partition p1 values in (2,4,6,8,0)
);
Hash分区
我们在实际工作中经常遇到像会员表的这种表。并没有明显可以分区的特征字段。但表数据有非常庞大。为了把这类的数据进行分区打散mysql 提供了hash分区。基于给定的分区个数,将数据分配到不同的分区,HASH分区只能针对整数进行HASH,对于非整形的字段只能通过表达式将其转换成整数。表达式可以是mysql中任意有效的函数或者表达式,对于非整形的HASH往表插入数据的过程中会多一步表达式的计算操作,所以不建议使用复杂的表达式这样会影响性能。
Hash分区表的基本语句如下:
CREATE TABLE my_member (
id INT NOT NULL, fname VARCHAR(30),
lname VARCHAR(30),
created DATE NOT NULL DEFAULT '1970-01-01',
separated DATE NOT NULL DEFAULT '9999-12-31',
job_code INT,
store_id INT )
PARTITION BY HASH(id)
PARTITIONS 4;
注意:
HASH分区可以不用指定PARTITIONS子句,如上文中的PARTITIONS 4,则默认分区数为1。
不允许只写PARTITIONS,而不指定分区数。
同RANGE分区和LIST分区一样,PARTITION BY HASH (expr)子句中的expr返回的必须是整数值。
HASH分区的底层实现其实是基于MOD函数。譬如,对于下表
CREATE TABLE t1 (
col1 INT,
col2 CHAR(5),
col3 DATE)
PARTITION BY HASH( YEAR(col3) )
PARTITIONS 4;
如果你要插入一个col3为“2017-09-15”的记录,则分区的选择是根据以下值决定的:
MOD(YEAR(‘2017-09-01’),4) = MOD(2017,4) = 1
LINEAR HASH分区
LINEAR HASH分区是HASH分区的一种特殊类型,与HASH分区是基于MOD函数不同的是,它基于的是另外一种算法。
格式如下:
CREATE TABLE my_members (
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 LINEAR HASH( id )
PARTITIONS 4;
说明: 它的优点是在数据量大的场景,譬如TB级,增加、删除、合并和拆分分区会更快,缺点是,相对于HASH分区,它数据分布不均匀的概率更大。
KEY分区
KEY分区其实跟HASH分区差不多,不同点如下:
KEY分区允许多列,而HASH分区只允许一列。
如果在有主键或者唯一键的情况下,key中分区列可不指定,默认为主键或者唯一键,如果没有,则必须显性指定列。
KEY分区对象必须为列,而不能是基于列的表达式。
KEY分区和HASH分区的算法不一样,PARTITION BY HASH (expr),MOD取值的对象是expr返回的值,而PARTITION BY KEY (column_list),基于的是列的MD5值。
格式如下:
CREATE TABLE k1 (
id INT NOT NULL PRIMARY KEY,
name VARCHAR(20) )
PARTITION BY KEY()
PARTITIONS 2;
在没有主键或者唯一键的情况下,格式如下:
CREATE TABLE tm1 (
s1 CHAR(32) )
PARTITION BY KEY(s1)
PARTITIONS 10;
一、RANGE partitioning
CREATE TABLE members01 (
id int(11) NOT NULL AUTO_INCREMENT,
username VARCHAR(16) NOT NULL,
email VARCHAR(35),
joined DATETIME NOT NULL,
PRIMARY KEY (id,joined),
UNIQUE KEY `uk_username` (username,email,joined)
)
PARTITION BY RANGE( TO_DAYS(joined) ) (
PARTITION p20170801 VALUES LESS THAN (736908),
PARTITION p20170802 VALUES LESS THAN (736909)
);
这种是最常见的,也是我们MDB平台提供自动按天见分区的格式。一般也比较适合按天分区,或者固定范围的分区,比如时间范围,只能按照数字大小(年龄/编号)进行区间划分。
优势:
1、按分区快速淘汰历史数据
2、按分区字段的范围查询
二、LIST partitioning
CREATE TABLE members02 (
id int(11) NOT NULL AUTO_INCREMENT,
username VARCHAR(16) NOT NULL,
email VARCHAR(35),
joined DATETIME NOT NULL,
PRIMARY KEY (id,joined),
UNIQUE KEY `uk_username` (username,email,joined)
)
PARTITION BY LIST( TO_DAYS(joined) ) (
PARTITION p20170801 VALUES IN (736905,736907),
PARTITION p20170803 VALUES IN (736908,736909)
);
表面上看,咦?好像使用list分区的都可以使用rang分区实现呢,其实大部分场景两种分区方式都是可以实现的,线上实际只能使用list分区的场景也比较少。
连续数据更趋向于使用range分区, list分区一般比较适合离散数据的分区,同时可以将多个离散的属性归类存储,比如我需要把20170801、20170803、20170809三个时间的数据放一个分区,20170802、20170805、20170808放个分区,这种就适合使用list分区,针对自己业务特性进行离散的分区,可以非常灵活的将数据打散到不同的分区。可以看出这种分区策略就不适合where条件的范围查询,适合固定值的in条件查询。
优势:
1、灵活的离散数据分区,可自定义分区list规则。
2、 离散分区不适合where条件date>20170801 and date >20170809,适合固定分区的等值查询或in条件查询
HASH partitioning
CREATE TABLE members03 (
id int(11) NOT NULL AUTO_INCREMENT,
username VARCHAR(16) NOT NULL,
email VARCHAR(35),
joined DATETIME NOT NULL,
PRIMARY KEY (id,joined),
UNIQUE KEY `uk_username` (username,email,joined)
)
PARTITION BY hash(TO_DAYS(joined))
PARTITIONS 2;
hash分区很好理解,就是对指定列做hash,均匀的存到指定的分区,比如按用户名hash分区,那么按用户名进行查找的速度就会快很多,这种针对分区列数据不固定,想把数据根据分区列离散的存储到固定分区数的表中,不需要做数据淘汰的场景比较适合。
优势:
1、维护简单,分区数固定,根据hash自动分区。
2、适合固定条件的等值查询
3、对于分区列数据不固定,分区列值不固定(不适合list),可根据hash值均匀打散数据到不同分区。
KEY partitioning
CREATE TABLE members04 (
id int(11) NOT NULL AUTO_INCREMENT,
username VARCHAR(16) NOT NULL,
email VARCHAR(35),
joined DATETIME NOT NULL,
PRIMARY KEY (id,joined),
UNIQUE KEY `uk_username` (username,email,joined)
)
PARTITION BY key(joined)
PARTITIONS 4;
同样,使用key分区跟hash分区有着神奇的相似,不同的是,如果表有主键或者唯一键的时候无需指定key的列名,key分区自动根据键值进行分区。
优势:
对于有主键的表,可无需关心分区列,MySQL自行根据主键/唯一键分区。如果主键设置不合理,查询条件都不带主键,查询性能会很差。
删除分区
移除分区:ALTER TABLE tablename REMOVE PARTITIONING ;
删除分区:ALTER TABLE tablename DROP PARTITIONING ;
移除分区仅仅修改表分区定义,数据不会被删除;删除分区会删除分区定义同时删除分区上的数据。