mysql-分区表

分区表

分区的过程是将一个表或者索引分解为多个更小、更可管理的部分。从逻辑上将,只有一个表或者索引,但是在物理上这个表或索引可能由数十个物理分区组成。每个分区都是独立的对象,可以独自处理,也可以作为一个更大对象的一部分进行处理。

MySQL只支持水平分区,不支持垂直分区。
水平分区:将同一表中不同行的记录分配到不同的物理文件中。
垂直分区:将同一表中不同列的记录分配到不同的物理文件中。

MySQL数据库的分区是局部分区索引。一个分区中既存放了数据又存放了索引。而全局分区索引指的是数据存放在各个分区中,但是所有数据的索引放在一个对象中。MySQL暂时还不支持全局分区索引。

分表和分区的区别

**什么是分表?**

分表是将一个大表按照一定的规则分解成多张具有独立存储空间的实体表,我们可以称为子表,每个表都对应三个文件,MYD数据文件,.MYI索引文件,.frm表结构文件。这些子表可以分布在同一块磁盘上,也可以在不同的机器上。app读写的时候根据事先定义好的规则得到对应的子表名,然后去操作它。

**什么是分区?**

分区和分表相似,都是按照规则分解表。不同在于分表将大表分解为若干个独立的实体表,而分区是将数据分段划分在多个位置存放,可以是同一块磁盘也可以在不同的机器。分区后,表面上还是一张表,但数据散列到多个位置了。app读写的时候操作的还是大表名字,db自动去组织分区的数据。
分区的一个目的是为了将数据按照一个较粗的粒度分在不同的表中,这样做可以将相关的数据存放在一起,另外如果想一次批量删除整个分区的数据也变得方便。

分区表的适用场景

1:表非常大以至于无法全部放在内存中,或者只在标的最后部分有热点数据,其他均是历史数据
2:分区表的数据更容易维护。例如想批量删除大量数据可以使用清除整个分区的方式。另外还可以对一个独立分区进行优化、检查、修复等操作。
3:分区表的数据可以分布在不同的物理设备上,从而高效的利用多个硬件设备

分区表的优缺点

 **优点**
1)分区表的数据可以分布在不同的物理设备上,从而高效地利用多个硬件设备。
2)和单个磁盘或者文件系统相比,可以存储更多数据 
3)优化查询。在where语句中包含分区条件时,可以只扫描一个或多个分区表来提高查询效率;涉及到例如SUM()和COUNT()这样聚合函数的查询,可以很容易地进行并行处理。这种查询的一个简单例子如 “SELECT salesperson_id, COUNT (orders) as order_total FROM sales GROUP BY salesperson_id;”。通过“并行”,这意味着该查询可以在每个分区上同时进行,最终结果只需通过总计所有分区得到的结果。
4)分区表更容易维护。例如:对于那些已经失去保存意义的数据,通常可以通过删除与那些数据有关的分区,很容易地删除那些数据。相反地,在某些情况下,添加新数据的过程又可以通过为那些新数据专门增加一个新的分区,来很方便地实现。
5)通过跨多个磁盘来分散数据查询,来获得更大的查询吞吐量。
6)一些查询可以得到极大的优化,这主要是借助于满足一个给定WHERE语句的数据可以只保存在一个或多个分区内,这样在查找时就不用查找其他剩余的分区。因为分区可以在创建了分区表后进行修改,所以在第一次配置分区方案时还不曾这么做时,可以重新组织数据,来提高那些常用查询的效率。

**缺点**
1)如果分区字段中有主键或者唯一索引的列,那么多有主键列和唯一索引列都必须包含进来。即:分区字段要么不包含主键或者索引列,要么包含全部主键和索引列。
2)分区表中无法使用外键约束 
3)MySQL的分区适用于一个表的所有数据和索引,不能只对表数据分区而不对索引分区,也不能只对索引分区而不对表分区,也不能只对表的一部分数据分区。
4)MySQL数据库支持的分区类型为水平分区,并不支持垂直分区,因此,MySQL数据库的分区中索引是局部分区索引,一个分区中既存放了数据又存放了索引,而全局分区是指的数据库放在各个分区中,但是所有的数据的索引放在另外一个对象中

分区表的操作逻辑

SELECT 查询:当查询一个分区表的时候,分区层先打开并锁住所有底层表,优化器先判断是否可以过滤部分分区,然后再调用对应的存储引擎接口访问各个分区的数据;
INSERT操作:当写入一条数据时,分区层先打开并锁住所有的底层表,然后确定那个分区接收这条数据,再将记录写入对应的底层表;
DELETE操作:当删除一条记录时,分区层现代开并锁住所有的底层表,然后确定数据对应的分区,最后对相应底层进行删除操作;UPDATE操作:当更新一条记录时,分区层先打开并锁住所有的底层表,MYSQL先确定需要更新的记录在哪个分区,然后取出数据并更新,在判断更新后的数据应该放在哪个分区,最后对底层表进行写入操作,并对原数据所在的底层表进行删除操作。

当然其中有些操作是支持过滤的。例如当删除一条记录时,MYSQL 需要先找到这条记录,如果where条件恰好和分区表达式匹配,就可以将所有不包含这条记录的分区都过滤掉。同样的操作对于update同样有效。如果是insert操作,其本身就是只命中一个分区 ,其他分区都会被过滤掉。MYSQL先确定这条记录属于哪个分区,再将记录写入对应的底层分区表,无须对任何其他分区进行操作。(虽然每个操作都会“”“先打开并锁住所有的底层表”,但是并不是说分区表在处理过程中是锁住全表的,如果存储引擎能够自己实现行级锁,例如InnoDB,则会在分区层释放对应表锁)。

判断是否支持分区

  mysql> show variables like '%partition%';
    +-------------------+-------+
    | Variable_name     | Value |
    +-------------------+-------+
    | have_partitioning | YES   |
    +-------------------+-------+
    1 row in set (0.00 sec)

命令:show variables like ‘%partition%’ (have_partintioning 的值为YES,表示支持分区。

mysql支持的分区类型

1)RANGE分区:按照数据的区间范围分区

2)LIST分区:按照List中的值分区,与RANGE的区别是,range分区的区间范围值是连续的。

3)HASH分区

4)KEY分区 说明 在MySQL5.1版本中,RANGE,LIST,HASH分区要求分区键必须是INT类型,或者通过表达式返回INT类型。但KEY分区的时候,可以使用其他类型的列(BLOB,TEXT类型除外)作为分区键。

1 RANGE分区:

> create table t1 (id int)
partition by range(id)(
partition p0 values less than (10),
partition p1 values less than (20));

当数据小于10的时候,插入p0分区。大于等于10小于20时候,插入p1分区。

> INSERT INTO t1 SELECT 12;
> INSERT INTO t1 SELECT 2;

表物理文件变成了下面这种:

-rw-rw---- 1 mariadb mariadb 98304 2016-08-07 10:14 t1#P#p0.ibd
-rw-rw---- 1 mariadb mariadb 98304 2016-08-07 10:17 t1#P#p1.ibd

从表面上,看不出来到底插入到什么分区中了,可以用下面的命令查看:

SELECT * from information_schema.PARTITIONS where table_schema=database() and table_name='t1'

结果如下:

å¨è¿éæå¥å¾çæè¿°

å¨è¿éæå¥å¾çæè¿°

注意事项:
1)INSERT INTO t1 SELECT 32; # 这个插入会报错,因为我们上面定义的分区,并不包含这个区间。
对此,要允许插入大数的话,可以修改下表:

ALTER TABLE t1 add partition( partition p2 values less than (30)); 
或者 
ALTER TABLE t1 add partition( partition p2 values less than maxvalue );

2)每个分区都是按顺序定义的,从最低到最高。上面的语句,如果将less than(10) 和less than (20)的顺序颠倒过来,那么将报错,如下:

ERROR 1493 (HY000): VALUES LESS THAN value must be strictly increasing for each partition

3)Range分区中,分区键的值如果是NULL,将被作为一个最小值来处理。

RANGE分区主要用于日期列的分区,例如对于销售类的表,可以根据年来分区存放销售记录。如下面的分区表sales:

CREATE TABLE sales (
money INT UNSIGNED NOT NULL,
`date` DATETIME
) ENGINE=INNODB 
PARTITION BY RANGE (YEAR(DATE)) (
PARTITION p2008 VALUES LESS THAN (2009),
PARTITION p2009 VALUES LESS THAN (2010), 
PARTITION p2010 VALUES LESS THAN (2011) ); 
  > INSERT INTO sales SELECT 2399,'2008-04-20'; 
  > INSERT INTO sales SELECT 6569,'2009-01-25';
  > INSERT INTO sales SELECT 2399,'2010-12-20';

这样的话,不同年份的数据就插入到不同的分区中,便于对sales这张表进行管理。
如果要删除2008年的数据,不需要执行delete from sales where date>=’2008-01-01’ and date<=’2008-12-31’; 只要删除2008年数据所在的分区即可:

> alter table sales drop partition p2008;

分区的另一个好处是:
加快某些查询,例如我们只要查询2009年整年的销售额,如下即可:

 explain partitions select * from sales where date >='2009-01-01' and date <='2009-12-31'\G ***************************[ 1. row ]*************************** 
 id | 1 
 select_type | SIMPLE
 table | sales
 partitions | p2009 # 只去p2009这个分区去搜索
 type | ALL
 possible_keys | None
 key | None
 key_len | None
 ref | None
 rows | 2
 Extra | Using where

最常用的就是range分区。
但是注意:如果分区键是timestamp类型的,则必须用UNIX_TIMESTAMP转换下。如下例子:

ALTER TABLE `order_his_tmp` drop primary key, add primary key(id,order_time);  去掉原先的主键,加一个带分区ID的主键。
ALTER TABLE `order_his_tmp` PARTITION BY RANGE (UNIX_TIMESTAMP (order_time)) ( 
 PARTITION  p201508  VALUES LESS THAN  (UNIX_TIMESTAMP('2015-09-01')) ,
 PARTITION  p201509  VALUES LESS THAN  (UNIX_TIMESTAMP('2015-10-01')) ,
 PARTITION  P201510  VALUES LESS THAN  (UNIX_TIMESTAMP('2015-11-01')) ,
 PARTITION  P201511  VALUES LESS THAN  (UNIX_TIMESTAMP('2015-12-01')) ,
 PARTITION  P201512  VALUES LESS THAN  (UNIX_TIMESTAMP('2016-01-01')) ,
 PARTITION  P201601  VALUES LESS THAN  (UNIX_TIMESTAMP('2016-02-01')) ,
 PARTITION  P201602  VALUES LESS THAN  (UNIX_TIMESTAMP('2016-03-01')) );

对于分区键是DATETIME的,要用TO_DAYS()函数操作,如下例子:

> CREATE TABLE sales(
money int unsigned not null, 
date datetime) 
partition by range (TO_DAYS(date)) ( 
partition p201001 values less than (TO_DAYS('2010-02-01')), 
partition p201002 values less than (TO_DAYS('2010-03-01')), 
partition p201003 values less than (TO_DAYS('2010-04-01')) );

2 LIST分区:

和range分区类似,只是分区列的值是散列的,非连续的。

create Table t(
a INT,
b INT) ENGINE INNODB
PARTITION BY LIST(b) (
PARTITION p0 VALUES IN (1,3,5,7,9),
PARTITION p1 VALUES IN (2,4,6,8));

注意:list分区中使用的是VALUES IN 这种格式。

> insert into t select 3,2;
> insert into t select 2,12;  执行这行插入语句会报错,因为按照LIST(b)划分的话,12不在上述的LIST里面。
> insert into t select 3,4;  
> insert into t select 3,5;

查看分区:

> SELECT table_name,partition_name,table_rows from information_schema.Partitions where table_name='t' and table_schema=DATABASE()\G

***************************[ 1. row ]*************************** 
table_name | t 
partition_name | p0 
table_rows | 1 
***************************[ 2. row ]*************************** 
table_name | t 
partition_name | p1 
table_rows | 2 表示p1分区有2行数据

注意:
InnoDB和MyISAM在遇到一次性插入多条数据中出现分区未定义错误的时候处理方式是不同的。
InnoDB会把整个SQL语句当做一个事务,只要有错误,就完全不执行。而MyISAM则会将错误之前的sql都执行成功。

3 HASH分区:

HASH分区的目的是将数据均匀地分布到预先定义的各个分区中,保证各分区的数据数量大致都是一样的。
在RANGE和LIST分区中,必须明确指定一个给定的列值或列值集合应该保存在哪个分区中。在HASH分区中,MySQL自动完成这些工作,用于所要做的只是基于将要进行哈希分区的列值指定一个列值或表达式,以及指定备份去的表将要被分割成的分区数量。
要使用HASH分区来分割一个表,要在CREATE TABLE语句上添加一个 PARTITION BY HASH(expr) 子句,其中expr是一个返回一个整数的表达式。它可以仅仅是字段类型为MySQL整型的列名。

> create table t_hash (
a int,
b datetime ) engine innodb 
PARTITION BY HASH (YEAR(b)) 
PARTITIONS 4; 
>   INSERT INTO t_hash VALUES (6,'2013-03-11 12:21:22');

上面这条插入的话,实际上要执行MOD(2013,4)取余数,得出这行数据存放在哪个分区中。

> SELECT table_name,partition_name,table_rows FROM information_schema.`PARTITIONS` WHERE table_schema=DATABASE() AND table_name='t_hash' ;
> 结果如下:
+------------+----------------+------------+ 
| table_name | partition_name | table_rows | 
+------------+----------------+------------+ 
| t_hash     | p0             | 0 | 
| t_hash     | p1             | 1 | # 可以看到p1插入了4条数据,因为2013年除以4,余数是1。因此2013年都数据都落在p1分区 
| t_hash     | p2             | 0 | 
| t_hash     | p3             | 0 | 
+------------+----------------+------------+

4 LINEAR HASH分区:(线性hash)

mysql还支持这种复杂的分区算法。语法和HASH分区类似,只是将关键字HASH改成了LINEAR HASH。

如下:

> CREATE TABLE t_l_hash (
a INT,
b DATETIME
) ENGINE INNODB
PARTITION BY LINEAR HASH(YEAR(b))
PARTITIONS 4;

取大于分区数量4的下一个2的幂值V,V=POWER(2,CEILING(LOG(2,num)))=4
所在分区N=YEAR(‘2010-04-01’)&(V-1)=2.
LINEAR HASH分区的
优点:
增加、删除、合并和拆分分区将变得更加快捷,有利于处理含有大量数据的表。
缺点:
与使用HASH分区得到的数据分布相比,各个分区间数据的分布可能不大均衡。

5 KEY分区:

和HASH分区类似,不同之处在于HASH分区使用用户定义的函数进行分区,KEY分区使用MySQL数据库提供的函数进行分区。
对于NDB Cluster引擎,MySQL数据库使用MD5函数来分区;对于其他引擎,使用MySQL内部的哈希函数来分区

> CREATE TABLE t_key (
a INT,
b DATETIME
) ENGINE INNODB
PARTITION BY KEY(b)
PARTITIONS 4 ;

6 COLUMNS分区【很常用】:

前面的几种分区都是有条件限制的。条件是:必须是整型,如果不是整型,那么也必须是可以通过函数转换为整型的,如YEAR()、TO_DAYS()、MONTH()等函数。
MySQL5.5版本开始支持的COLUMNS分区,可视为RANGE分区和LIST分区的一种进化。
COLUMNS分区看直接使用非整型的数据进行分区,分区根据类型直接比较而得,不需要转换为整型。
此外,COLUMNS分区可以对多个列的值进行分区。
COLUMNS分区支持以下的数据类型:
1 所有的整型类型
支持:INT、SMALLINT、TINYINT、BIGINT。不支持:FLOAT和DECIMAL
2 日期类型 【常用】
支持 DATE、DATETIME
3 字符串类型
支持 CAHR、VARCHAR、BINARY、VARBINARY。不支持BLOB和TEXT

 CREATE TABLE t_columns_range ( 
 a INT,
  b DATETIME )
  ENGINE INNODB 
  PARTITION BY RANGE COLUMNS (B) ( 
  PARTITION p0 VALUES LESS THAN ('2009-01-01'), 
  PARTITION p1 VALUES LESS THAN ('2010-01-01') );

对于现有的表改成分区表

ALTER TABLE `tb_detail` drop primary key ,add primary key (id, bill_date); ALTER TABLE `tb_detail` PARTITION BY RANGE COLUMNS (bill_date)   (   
PARTITION  p201509  VALUES LESS THAN  ('2015-10-01') ,   
PARTITION  P201510  VALUES LESS THAN  ('2015-11-01') ,
PARTITION  P201511  VALUES LESS THAN  ('2015-12-01') ,
PARTITION  P201512  VALUES LESS THAN  ('2016-01-01') ,
PARTITION  P201601  VALUES LESS THAN  ('2016-02-01') ,
PARTITION  P201602  VALUES LESS THAN  ('2016-03-01') ,
PARTITION  P201603  VALUES LESS THAN  ('2016-04-01') ,
PARTITION  P201604  VALUES LESS THAN  ('2016-05-01') );

对于RANGE COLUMNS分区,可以使用多个列进行分区,如:

> CREATE TABLE rcx (
a INT,
b INT,
c CHAR(3),
d INT
)ENGINE INNODB
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)
);

子分区:
子分区(subpartitioning)是在分区的基础上在进行分区,有时也称这种分区为复合分区(composite partitioning)。
MySQL数据库允许在RANGE和LIST分区上再进行HASH或KEY的子分区。如:

> CREATE TABLE ts ( 
a INT, 
b DATE 
) ENGINE=INNODB 
PARTITION BY RANGE(YEAR(b)) 
SUBPARTITION BY HASH(TO_DAYS(b)) 
SUBPARTITIONS 2 ( 
PARTITION p0 VALUES LESS THAN (1990), 
PARTITION p1 VALUES LESS THAN (2000), 
PARTITION p2 VALUES LESS THAN MAXVALUE);

在物理文件上表示形式如下:

-rw-rw---- 1 mariadb mariadb 711 2016-08-07 19:28 ts.frm 
-rw-rw---- 1 mariadb mariadb 108 2016-08-07 19:28 ts.par 
-rw-rw---- 1 mariadb mariadb 98304 2016-08-07 19:28 ts#P#p0#SP#p0sp0.ibd 
-rw-rw---- 1 mariadb mariadb 98304 2016-08-07 19:28 ts#P#p0#SP#p0sp1.ibd 
-rw-rw---- 1 mariadb mariadb 98304 2016-08-07 19:28 ts#P#p1#SP#p1sp0.ibd 
-rw-rw---- 1 mariadb mariadb 98304 2016-08-07 19:28 ts#P#p1#SP#p1sp1.ibd 
-rw-rw---- 1 mariadb mariadb 98304 2016-08-07 19:28 ts#P#p2#SP#p2sp0.ibd 
-rw-rw---- 1 mariadb mariadb 98304 2016-08-07 19:28 ts#P#p2#SP#p2sp1.ibd

子分区的建立需要注意下面问题:
1、每个子分区的数量必须相同。
2、要在一个分区表的任何分区上使用SUBPARTITION来明确定义任何子分区,就必须定义所有的子分区。
3、每个SUBPARTITION子句必须包括子分区的一个名字。
4、子分区的名字必须是唯一的。

分区中的NULL值处理

分区中的NULL值:
MySQL允许对NULL值做分区。但是处理的方法与其他数据库完全不同。
MySQL数据库的分区总是视NULL值小于任何的一个非NULL值,这和MySQL数据库中处理NULL值的ORDER BY操作是一样的。因此对于不同的分区类型,MySQL数据库对于NULL值的处理也是各不相同。

1、RANGE分区中使用NULL值:

> CREATE table t_range( 
a int, 
b int) 
partition by range(b) ( 
partition p0 VALUES less than (10), 
partition p1 VALUES less than (20), 
partition p2 values less than maxvalue);

> insert into t_range select 1,1;
> insert into t_range select 2,null;

> SELECT table_name,partition_name,table_rows
FROM information_schema.`PARTITIONS`
WHERE table_schema=DATABASE() AND table_name='t_range';

结果如下:

+------------+----------------+------------+ 
| table_name | partition_name | table_rows |
+------------+----------------+------------+ 
| t_range    | p0             |          2 |
| t_range    | p1             |          0 | 
| t_range    | p2             |          0 |
+------------+----------------+------------+

可以看到插入的数据都放到了p0分区。
也就是说在RANGE分区下,NULL值会放入最左边的分区中。
另外,如果删除了p0分区,删除的将是小于10的记录和NULL值的记录,这点非常重要。

> alter table t_range drop partition p0;
> SELECT * from t_range;  可以看到下图的表已经为空值了

+-----+-----+
| a   | b   |
|-----+-----|
+-----+-----+

2、LIST分区下使用NULL值
LIST分区下使用NULL值,必须显式的指出哪个分区中放入NULL值,否则会报错。
如下写法才能允许插入NULL值。

CREATE table t_list ( 
a int, 
b int) 
partition by list(b) ( 
partition p0 values in (1,3,5,7,9,NULL), # 注意必须显式的指出NULL插入到那个分区才行 
partition p1 VALUES in (2,4,6,8,10));

> INSERT INTO t_list SELECT 1,4;
> INSERT INTO t_list SELECT 3,null;

> SELECT table_name,partition_name,table_rows
IFROM information_schema.`PARTITIONS`
WHERE table_schema=DATABASE() AND table_name='t_list'\G

结果如下:
***************************[ 1. row ]*************************** 
table_name     | t_list 
partition_name | p0 
table_rows     | 1 
***************************[ 2. row ]*************************** 
table_name     | t_list 
partition_name | p1 
table_rows     | 1

3、HASH和KEY分区对于NULL的处理方式
HASH和KEY分区对于NULL的处理方式和RANGE分区、LIST分区不一样。
任何分区函数都会将含有NULL值的记录返回为0。【返回0的说明是存放在第一个分区中】

> create table t_hash (
a int,
b int ) engine=innodb
partition by hash(b)
partitions 4;

> INSERT INTO t_hash SELECT 3,null;
> INSERT INTO t_hash SELECT 3,6778;
> SELECT table_name,partition_name,table_rows
FROM information_schema.`PARTITIONS`
WHERE table_schema=DATABASE() AND table_name='t_hash'\G

结果如下:
***************************[ 1. row ]*************************** 
table_name     | t_hash 
partition_name | p0 
table_rows | 1 
***************************[ 2. row ]*************************** 
table_name     | t_hash 
partition_name | p1 
table_rows | 0 
***************************[ 3. row ]*************************** 
table_name     | t_hash 
partition_name | p2 
table_rows | 1 
***************************[ 4. row ]*************************** 
table_name     | t_hash 
partition_name | p3 
table_rows | 0

转自:https://blog.csdn.net/xuduorui/article/details/80720111

猜你喜欢

转载自blog.csdn.net/weixin_42782897/article/details/88422836
今日推荐