mysql分区
Mysql支持水平分区,并不支持垂直分区;
水平分区:指将同一表中不同行的记录分配到不同的物理文件中;
垂直分区:指将同一表中不同列的记录分配到不同的物理文件中;
其中CSV、FEDORATED、MERGE等引擎不支持分区,MYISAM、InnoDB、NDB等引擎支持分区
目的
将一个表或索引分解为多个更小、更可管理的部分,从逻辑上讲,只有一个表或者索引,但是物理上这个表或者索引可能由数十个物理分区组成;每个分区都是独立的对象,可以独自处理,也可以作为一个更大对象的一部分进行处理(如果分区表很大,亦可以将分区分配到不同的磁盘上去);在执行查询的时候,优化器会根据分区定义过滤哪些没有我们需要数据的分区,这样查询就无须全表扫描所有分区,只查找包含需要数据的分区即可
适用场景
- 表非常大以至于无法全部都放到内存,或者只在表的最后部分有热点数据,其他均为历史数据
- 分区表数据更容易维护(可独立对分区进行优化、检查、修复及批量删除大数据可以采用drop分区的形式等)
- 分区表的数据可以分布在不同的物理设备上,从而高效地利用多个硬件设备
- 分区表可以避免某些特殊的瓶颈(ps: InnoDB的单个索引的互斥访问、ext3文件系统的inode锁竞争等)
- 可以备份和恢复独立的分区,非常适用于大数据集的场景
分区表限制
- 单表最多支持1024个分区
- MySQL5.1只能对数据表的整型列进行分区,或者数据列可以通过分区函数转化成整型列;
- MySQL5.5的RANGE LIST类型可以直接使用列进行分区
- 如果分区字段中有主键或唯一索引的列,那么所有的主键列和唯一索引列都必须包含进来
- 分区表无法使用外键约束
- 分区必须使用相同的Engine
- 对于MyISAM分区表,不能在使用LOAD INDEX INTO CACHE操作
- 对于MyISAM分区表,使用时会打开更多的文件描述符(单个分区是一个独立的文件)
分区策略
- 全量扫描数据,不需要任何索引:通过where条件大概定位哪个分区,必须将查询所需要扫描的分区个数限制在很小的数量
- 建立分区索引,分离热点:如将明显的热点数据分离到一个分区,使其尽量缓存到内存中,这样就能充分使用索引和缓存
- 注意:以上策略均以查询得到过滤,丢掉额外的分区,分区本身不产生额外的代价为准则】
分区表使用过程的坑
- NULL值会使分区过滤无效:
分表的表达式的值可以是NULL,第一个分区为特殊分区存放NULL或者非法值
如: PARTITION BY RANGE YEAR(order_date)进行分区,那么order_date为NULL或者非法值,记录存放在第一个分区:
WHERE order_date BETWEEN ‘2014-01-01’ AND ‘2014-01-31’查询时会检查两个分区:
第一个分区及1月份分区,避免第一分区数据过大时造成查询代价过高,可以使用:建立第一分区专门存放order_date为NULL和非法值记录 PARTITION p_nulls VALUES LESS THAN(0)
MySQL5.5以后可以才用一下语法解决问题: PARTITION BY RANGE COLUMNS(order_date)
- 分区列和索引列不匹配
此种情况下查询无法进行分区过滤,分区失效除非查询中包含了可以过滤分区的条件
RANGE类型分区随着分区数量增加会对MYSQL额外增加查询分区定义列表(符合条件行在哪个分区)的压力,尽量限制适当的分区数量;key和hash类型分区不存在此问题
重组分区或者类似alter语句可能会造成很大的开销
新建或者删除分区操作很快,重组分区或者类似ALTER语句操作会先创建一个临时的分区,将数据复制其中,然后在删除原分区
分区表类型
- RANGE分区:行数据基于属于一个给定连续区间的列值被放入分区
MySQL5.5开始支持RANGE COLUMNS的分区(引入Columns分区解决了MySQL 5.5版本之前RANGE分区和LIST分区只支持整数分区,从而导致需要额外的函数计算得到整数或者通过额外的转换表来转换为整数再分区的问题。Columns分区可以细分为RANGE Columns分区和LIST Columns分区,RANGE Columns分区和LIST Columns分区都支持整数、日期时间、字符串三大数据类型)
LIST分区:类似于按RANGE分区,区别在于LIST分区是基于列值匹配一个离散值集合中的某个值来进行选择。
MySQL5.5开始支持RANGE COLUMNS的分区COLUMNS分区:可以无需通过表达式进行转换直接对非整形字段进行分区,同时COLUMNS分区还支持多个字段组合分区,只有RANGELIST存在COLUMNS分区,COLUMNS是RANGE和LIST分区的升级。
HASH分区:基于给定的分区个数,将数据分配到不同的分区,HASH分区只能针对整数进行HASH,对于非整形的字段只能通过表达式将其转换成整数。
KEY分区:根据MySQLS数据库提供的哈希函数来进行分区 【注:无论创建何种类型的分区,如果表中存在主键或唯一索引时,分区列必须是唯一索引的一个组成部分】支持除text和BLOB之外的所有数据类型的分区,key分区可以直接基于字段做分区无需转换成整数。
说明
注意分区名的大小写敏感问题,和关键字问题。
无论哪种分区类型,要么分区表中没有主键或唯一键,要么主键或唯一键包含在分区列里面,对于存在主键或者唯一键的表不能使用主键或者唯一键之外的字段作为分区字段。
5.7以前的版本显示分区的执行计划使用:explain PARTITIONS;5.7以后直接执行:explain
没有强制要求分区列非空,建议分区的列为NOT NULL的列;在RANGE 分区中如果往分区列中插入NULL值会被当作最小的值来处理,在LIST分区中NULL值必须在枚举列表中否则插入失败,在HASH/KEY分区中NULL值会被当作0来处理。
基于时间类型的字段的转换函数mysql提供了"YEAR(),MONTH(),DAY(),TO_DAYS(),TO_SECONDS(),WEEKDAY(),DAYOFYEAR()"
拆分合并分区后会导致修改的分区的统计信息失效,没有修改的分区的统计信息还在,不影响新插入的值加入到统计信息;这时需要对表执行Analyze操作.
针对非整形字段进行RANG\LIST分区建议使用COLUMNS分区。
分区表相关操作
分区相关查询
查看当前数据库是否支持分区
mysql> show variables like '%partition%';
+---------------------------------------+-------+
| Variable_name | Value |
+---------------------------------------+-------+
| have_partitioning | YES |
| innodb_adaptive_hash_index_partitions | 1 |
+---------------------------------------+-------+
2 rows in set
查看创建分区表的CREATE语句
mysql>show create table operation_log;
查看表是否为分区表(Create_options)
mysql>show table status(当前库所有表状态)
mysql>show table status from lockrank like '%operation_log%';(lockrank库operation_log表状态)
Table: operation_log
Create Table: CREATE TABLE `operation_log` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`cid` mediumint(7) unsigned NOT NULL,
`accountid` mediumint(8) NOT NULL DEFAULT '0' ,
`flag` tinyint(1) unsigned NOT NULL DEFAULT '0',
`addtime` int(11) unsigned NOT NULL,
`device` tinyint(1) unsigned NOT NULL DEFAULT '1' ,
PRIMARY KEY (`id`,`addtime`),
KEY `idx_accountid_addtime` (`accountid`,`addtime`),
KEY `idx_accountid_flag` (`accountid`,`flag`),
) ENGINE=InnoDB AUTO_INCREMENT=50951039 DEFAULT CHARSET=utf8 COMMENT='操作记录'
/*!50100 PARTITION BY RANGE (addtime)
(PARTITION `2013-05` VALUES LESS THAN (1370016000) ENGINE = InnoDB,
PARTITION `2013-06` VALUES LESS THAN (1372608000) ENGINE = InnoDB,
PARTITION `2013-07` VALUES LESS THAN (1375286400) ENGINE = InnoDB,
PARTITION `2013-08` VALUES LESS THAN (1377964800) ENGINE = InnoDB,
PARTITION `2013-09` VALUES LESS THAN (1380556800) ENGINE = InnoDB,
PARTITION `2013-10` VALUES LESS THAN (1383235200) ENGINE = InnoDB,
PARTITION `2013-11` VALUES LESS THAN (1385827200) ENGINE = InnoDB,
PARTITION `2013-12` VALUES LESS THAN (1388505600) ENGINE = InnoDB,
PARTITION `2014-01` VALUES LESS THAN (1391184000) ENGINE = InnoDB,
PARTITION `2014-02` VALUES LESS THAN (1393603200) ENGINE = InnoDB,
PARTITION `2014-03` VALUES LESS THAN (1396281600) ENGINE = InnoDB,
PARTITION `2014-04` VALUES LESS THAN (1398873600) ENGINE = InnoDB,
PARTITION `2014-05` VALUES LESS THAN (1401552000) ENGINE = InnoDB,
PARTITION `2014-06` VALUES LESS THAN (1404144000) ENGINE = InnoDB,
PARTITION `2014-07` VALUES LESS THAN (1406822400) ENGINE = InnoDB,
PARTITION `2014-08` VALUES LESS THAN (1409500800) ENGINE = InnoDB,
PARTITION `2014-09` VALUES LESS THAN MAXVALUE ENGINE = InnoDB) */
1 row in set (0.00 sec)
查看select如何使用分区
mysql> explain partitions select id,accountid,cid,flag from operation_log where addtime="1369362524" \G ;
*************************** 1\. row ***************************
id: 1
select_type: SIMPLE
table: operation_log
partitions: 2013-05
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 4384356
Extra: Using where
1 row in set (0.00 sec)
分区表元数据统计表:INFORMATION_SCHEMA.PARTITIONS
查看分区表operation_log的分区信息
mysql> SELECT partition_name part, partition_expression expr, partition_description descr, table_rows FROM INFORMATION_SCHEMA.partitions WHERE TABLE_SCHEMA = schema() AND TABLE_NAME='operation_log';
+---------+---------+------------+------------+
| part| expr| descr | table_rows |
+---------+---------+------------+------------+
| 2013-05 | addtime | 1370016000 | 5999642 |
| 2013-06 | addtime | 1372608000 | 4579263 |
| 2013-07 | addtime | 1375286400 | 3223772 |
| 2013-08 | addtime | 1377964800 | 1995058 |
| 2013-09 | addtime | 1380556800 | 2497406 |
| 2013-10 | addtime | 1383235200 | 4106974 |
| 2013-11 | addtime | 1385827200 | 6209559 |
| 2013-12 | addtime | 1388505600 | 6415349 |
| 2014-01 | addtime | 1391184000 | 3953594 |
| 2014-02 | addtime | 1393603200 | 0 |
| 2014-03 | addtime | 1396281600 | 0 |
| 2014-04 | addtime | 1398873600 | 0 |
| 2014-05 | addtime | 1401552000 | 0 |
| 2014-06 | addtime | 1404144000 | 0 |
| 2014-07 | addtime | 1406822400 | 0 |
| 2014-08 | addtime | 1409500800 | 0 |
| 2014-09 | addtime | MAXVALUE | 0 |
+---------+---------+------------+------------+
17 rows in set (1.48 sec)
创建分区操作
- RANGE分区:
mysql> CREATE TABLE `operation_log` (
-> `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
-> `cid` mediumint(7) unsigned NOT NULL,
-> `accountid` mediumint(8) NOT NULL DEFAULT '0' ,
-> `flag` tinyint(1) unsigned NOT NULL DEFAULT '0',
-> `addtime` int(11) unsigned NOT NULL,
-> `device` tinyint(1) unsigned NOT NULL DEFAULT '1' ,
-> PRIMARY KEY (`id`,`addtime`),
-> KEY `idx_accountid_addtime` (`accountid`,`addtime`),
-> KEY `idx_accountid_flag` (`accountid`,`flag`),
->) ENGINE=InnoDB AUTO_INCREMENT=50951039 DEFAULT CHARSET=utf8 COMMENT='操作记录'
->/*!50100 PARTITION BY RANGE (addtime)
->(PARTITION `2013-05` VALUES LESS THAN (1370016000) ENGINE = InnoDB,
-> PARTITION `2013-06` VALUES LESS THAN (1372608000) ENGINE = InnoDB,
-> PARTITION `2013-07` VALUES LESS THAN (1375286400) ENGINE = InnoDB,
-> PARTITION `2013-08` VALUES LESS THAN (1377964800) ENGINE = InnoDB,
-> PARTITION `2013-09` VALUES LESS THAN MAXVALUE ENGINE = InnoDB) */;
1 row in set (0.00 sec)
( LESS THAN MAXVALUE考虑到可能的最大值)
-
list分区
//这种方式失败mysql> CREATE TABLE IF NOT EXISTS `list_part` ( -> `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户ID', -> `province_id` int(2) NOT NULL DEFAULT 0 COMMENT '省', -> `name` varchar(50) NOT NULL DEFAULT '' COMMENT '名称', -> `sex` int(1) NOT NULL DEFAULT '0' COMMENT '0为男,1为女', -> PRIMARY KEY (`id`) -> ) ENGINE=INNODB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 -> PARTITION BY LIST (province_id) ( -> PARTITION p0 VALUES IN (1,2,3,4,5,6,7,8), -> PARTITION p1 VALUES IN (9,10,11,12,16,21), -> PARTITION p2 VALUES IN (13,14,15,19), -> PARTITION p3 VALUES IN (17,18,20,22,23,24) -> ); ERROR 1503 (HY000): A PRIMARY KEY must include all columns in the table's partitioning function //这种方式成功 mysql> CREATE TABLE IF NOT EXISTS `list_part` ( -> `id` int(11) NOT NULL COMMENT '用户ID', -> `province_id` int(2) NOT NULL DEFAULT 0 COMMENT '省', -> `name` varchar(50) NOT NULL DEFAULT '' COMMENT '名称', -> `sex` int(1) NOT NULL DEFAULT '0' COMMENT '0为男,1为女' -> ) ENGINE=INNODB DEFAULT CHARSET=utf8 -> PARTITION BY LIST (province_id) ( -> PARTITION p0 VALUES IN (1,2,3,4,5,6,7,8), -> PARTITION p1 VALUES IN (9,10,11,12,16,21), -> PARTITION p2 VALUES IN (13,14,15,19), -> PARTITION p3 VALUES IN (17,18,20,22,23,24) -> ); Query OK, 0 rows affected (0.33 sec) 上面的这个创建list分区时,如果有主銉的话,分区时主键必须在其中,不然就会报错。如果我不用主键,分区就创建成功了,一般情况下,一个张表肯定会有一个主键,这算是一个分区的局限性
* hash分区
mysql> CREATE TABLE IF NOT EXISTS hash_part
(
-> id
int(11) NOT NULL AUTO_INCREMENT COMMENT '评论ID',
-> comment
varchar(1000) NOT NULL DEFAULT '' COMMENT '评论',
-> ip
varchar(25) NOT NULL DEFAULT '' COMMENT '来源IP',
-> PRIMARY KEY (id
)
-> ) ENGINE=INNODB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1
-> PARTITION BY HASH(id)
-> PARTITIONS 3;
Query OK, 0 rows affected (0.06 sec)
* key分区
mysql> CREATE TABLE IF NOT EXISTS key_part
(
-> news_id
int(11) NOT NULL COMMENT '新闻ID',
-> content
varchar(1000) NOT NULL DEFAULT '' COMMENT '新闻内容',
-> u_id
varchar(25) NOT NULL DEFAULT '' COMMENT '来源IP',
-> create_time
DATE NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '时间'
-> ) ENGINE=INNODB DEFAULT CHARSET=utf8
-> PARTITION BY LINEAR HASH(YEAR(create_time))
-> PARTITIONS 3;
Query OK, 0 rows affected (0.07 sec)
#### 增加子分区操作
>子分区是分区表中每个分区的再次分割,子分区既可以使用HASH希分区,也可以使用KEY分区。这 也被称为复合分区(composite partitioning)
1. 如果一个分区中创建了子分区,其他分区也要有子分区
2. 如果创建了了分区,每个分区中的子分区数必须相同
3. 同一分区内的子分区,名字不相同,不同分区内的子分区名子可以相同(5.1.50不适用)
mysql> CREATE TABLE IF NOT EXISTS sub_part
(
-> news_id
int(11) NOT NULL COMMENT '新闻ID',
-> content
varchar(1000) NOT NULL DEFAULT '' COMMENT '新闻内容',
-> u_id
int(11) NOT NULL DEFAULT 0s COMMENT '来源IP',
-> create_time
DATE NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '时间'
-> ) ENGINE=INNODB DEFAULT CHARSET=utf8
-> PARTITION BY RANGE(YEAR(create_time))
-> SUBPARTITION BY HASH(TO_DAYS(create_time))(
-> PARTITION p0 VALUES LESS THAN (1990)(SUBPARTITION s0,SUBPARTITION s1,SUBPARTITION s2),
-> PARTITION p1 VALUES LESS THAN (2000)(SUBPARTITION s3,SUBPARTITION s4,SUBPARTITION good),
-> PARTITION p2 VALUES LESS THAN MAXVALUE(SUBPARTITION tank0,SUBPARTITION tank1,SUBPARTITION tank3)
-> );
Query OK, 0 rows affected (0.07 sec)
### 分区管理
#### 增加分区操作(针对设置MAXVALUE)
##### range添加分区
mysql>alter table operation_log add partition(partition 2013-10
values less than (1383235200));
--->适用于没有设置MAXVALUE的分区添加
ERROR 1481 (HY000):MAXVALUE can only be used in last partition definition
mysql>alter table operation_log REORGANIZE partition 2013-09
into (partition 2013-09
values less than (1380556800),partition 2013-10
values less than (1383235200),partition 2013-11
values less than maxvalue);
##### list添加分区
mysql> alter table list_part add partition(partition p4 values in (25,26,28));
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
##### hash重新分区
mysql> alter table list_part add partition(partition p4 values in (25,26,28));
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
##### key重新分区
mysql> alter table key_part add partition partitions 4;
Query OK, 1 row affected (0.06 sec)//有数据也会被重新分配
Records: 1 Duplicates: 0 Warnings: 0
##### 子分区添加新分区,虽然我没有指定子分区,但是系统会给子分区命名的
mysql> alter table sub1_part add partition(partition p3 values less than MAXVALUE);
Query OK, 0 rows affected (0.02 sec)
Records: 0 Duplicates: 0 Warnings: 0
1.移除表的分区
ALTER TABLE tablename
REMOVE PARTITIONING ;
注意:使用remove移除分区是仅仅移除分区的定义,并不会删除数据和drop PARTITION不一样,后者会连同数据一起删除
2.对已经存在记录的表创建分区,以增加range分区为例,和创建表建分区的语法一样。
ALTER TABLE tb_partition
.tb_varchar
PARTITION BY RANGE(id) PARTITIONS 3( PARTITION part0 VALUES LESS THAN (5000), PARTITION part1 VALUES LESS THAN (10000), PARTITION part2 VALUES LESS THAN (MAXVALUE)) ;
注意:对已有的表创建分区之后,数据会按照分区的定义分布到各个分区文件当中
##### 删除分区操作
alter table user drop partition2013-05;
#### 分区表其他操作
* 重建分区(官方:与先drop所有记录然后reinsert是一样的效果;用于整理表碎片)
`alter table operation_log rebuild partition `2014-01`;`
* 重建多个分区
alter table operation_log rebuild partition 2014-01
,2014-02
;
过程如下:
pro
优化分区(如果删除了一个分区的大量记录或者对一个分区的varchar blob text数据类型的字段做了许多更新,此时可以对分区进行优化以回收未使用的空间和整理分区数据文件)
alter table operation_log optimize partition 2014-01
;
优化的操作相当于check partition,analyze partition 和repair patition
分析分区
alter table operation_log analyze partition 2014-01
;
修复分区
alter table operation_log repair partition 2014-01
;
检查分区
alter table operation_log check partition 2014-01
;
注释:
mysqlcheck、myisamchk并不支持分区表,analyze,check,optimize,rebuild,repair,truncate不支持子分区操作
在MySQL5.6中,可以使用清空一个分区数据:alter table operation_log truncate partition 2014-01;
清空该分区表所有分区数据:alter table operation_log truncate partition all;
更多详细可阅读 https://www.cnblogs.com/chenmh/p/5623474.html