本文概括了在MySQL5.7.32版本中如何创建分表和分区操作!!!
分表和分区
一、分表
1.什么是分表
数据库数据越来越大,随之而来的是单个表中数据太多。以至于查询速度慢,而且由于表的锁机制导致应用操作也受到影响,出现了数据库性能瓶颈。
MySQL中有一种机制是表锁定和行锁定,是为了保证数据的完整性。
- 表锁定:表示你们都不能对这张表进行操作,必须等我对表操作完成之后才可以进行操作;
- 行锁定:所表锁定差不多,别的sql语句必须等我这条数据操作完了,才能对这条数据进行操作;
当出现这种情况,我们就应该考虑分表或分区了!
2.MySQL分表
分表是将一个大表按照一定的规则分解成多张具有独立存储空间的实体表,每个表都对应三个文件,MYD数据文件,.MYI索引文件,.frm表结构文件。这些表可以分布在同一块磁盘上,也可以在不同的机器上。app读写的时候根据事先定义好的规则得到对应的表名,然后去操作它。
将单个数据库表进行拆分,拆分成多个数据表,然后用户访问的时候,根据一定的算法(如用hash的方式,也可以用求余(取模)的方式),让用户访问不同的表,这样数据分散到多个数据表中,减少了单个数据表的访问压力。提升了数据库访问性能。分表的目的就在于此,减小数据库的负担,缩短查询时间
3.分表方式分为
Mysql分表分为垂直切分和水平切分:
- 垂直切分:指数据表列的拆分,把一张列比较多的表拆分为多张表 通常我们按以下原则进行垂直拆分;把不常用的字段单独放在一张表; 把text,blob(binary large object,二进制大对象)等大字段拆分出来放在附表中;经常组合查询的列放在一张表中; 垂直拆分更多时候就应该在数据表设计之初就执行的步骤,然后查询的时候用join关键起来即可;
- 水平拆分:指数据表行的拆分,把一张的表的数据拆成多张表来存放。 水平拆分原则,通常情况下,我们使用hash、取模等方式来进行表的拆分 比如一张有400W的用户表users,为提高其查询效率我们把其分成4张表users1,users2,users3,users4 通过用ID取模的方法把数据分散到四张表内Id%4= [0,1,2,3] 然后查询,更新,删除也是通过取模的方法来查询 部分业务逻辑也可以通过地区,年份等字段来进行归档拆分; 进行拆分后的表,这时我们就要约束用户查询行为。比如我们是按年来进行拆分的,这个时候在页面设计上就约束用户必须要先选择年,然后才能进行查询。
4.利用merge存储引擎来实现分表
注意:
-
只有myisam引擎的原表才可以利用merge存储引擎实现分表!
-
merge分表,分为主表和子表,主表类似于一个壳子,逻辑上封装了子表,实际上数据都是存储在子表中的。 我们可以通过主表插入和查询数据,如果清楚分表规律,也可以直接操作子表。
1)创建一个完整表
mysql> create database test;
Query OK, 1 row affected (0.00 sec)
mysql> use test;
Database changed
mysql> create table member(
-> id bigint auto_increment primary key,
-> name varchar(20),
-> sex tinyint not null default '0'
-> )engine=myisam default charset=utf8 auto_increment=1;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into member(name,sex) values('tom1',1);
Query OK, 1 row affected (0.00 sec)
#多执行几次以下俩条命令就会有下面的数据!!!
mysql> insert into member(name,sex) select name,sex from member;
Query OK, 1 row affected (0.00 sec)
Records: 1 Duplicates: 0 Warnings: 0
mysql> insert into member(name,sex) select name,sex from member;
Query OK, 2 rows affected (0.00 sec)
Records: 2 Duplicates: 0 Warnings: 0
......
mysql> select * from member;
+----+------+-----+
| id | name | sex |
+----+------+-----+
| 1 | tom1 | 1 |
| 2 | tom1 | 1 |
| 3 | tom1 | 1 |
| 4 | tom1 | 1 |
| 5 | tom1 | 1 |
| 6 | tom1 | 1 |
| 7 | tom1 | 1 |
| 8 | tom1 | 1 |
| 9 | tom1 | 1 |
| 10 | tom1 | 1 |
| 11 | tom1 | 1 |
| 12 | tom1 | 1 |
| 13 | tom1 | 1 |
| 14 | tom1 | 1 |
| 15 | tom1 | 1 |
| 16 | tom1 | 1 |
+----+------+-----+
16 rows in set (0.00 sec)
2)对上面的主表进行分表
PS:把member分两个表tb_member1,tb_member2。
mysql> create table tb_member1(
-> id bigint primary key,
-> name varchar(20),
-> sex tinyint not null default '0'
-> )engine=myisam default charset=utf8;
Query OK, 0 rows affected (0.00 sec)
mysql> create table tb_member2(
-> id bigint primary key,
-> name varchar(20),
-> sex tinyint not null default '0'
-> )engine=myisam default charset=utf8;
Query OK, 0 rows affected (0.33 sec)
#表结构必须一致
mysql> desc tb_member1;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id | bigint(20) | NO | PRI | NULL | |
| name | varchar(20) | YES | | NULL | |
| sex | tinyint(4) | NO | | 0 | |
+-------+-------------+------+-----+---------+-------+
3 rows in set (0.01 sec)
mysql> desc tb_member2;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id | bigint(20) | NO | PRI | NULL | |
| name | varchar(20) | YES | | NULL | |
| sex | tinyint(4) | NO | | 0 | |
+-------+-------------+------+-----+---------+-------+
3 rows in set (0.00 sec)
或
#创建分表
mysql> create table tb_member1 like tb_member;
Query OK, 0 rows affected (0.00 sec)
mysql> create table tb_member2 like tb_member;
Query OK, 0 rows affected (0.00 sec)
#创建主表
mysql> create table tb_member(
-> id bigint primary key ,
-> name varchar(20),
-> sex tinyint not null default '0'
-> ) ENGINE=MERGE UNION=(tb_member1,tb_member2) INSERT_METHOD=LAST CHARSET=utf8 ;
Query OK, 0 rows affected (0.11 sec)
#查看表结构是否一致
mysql> desc tb_member;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id | bigint(20) | NO | PRI | NULL | |
| name | varchar(20) | YES | | NULL | |
| sex | tinyint(4) | NO | | 0 | |
+-------+-------------+------+-----+---------+-------+
3 rows in set (0.00 sec)
mysql> desc tb_member1;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id | bigint(20) | NO | PRI | NULL | |
| name | varchar(20) | YES | | NULL | |
| sex | tinyint(4) | NO | | 0 | |
+-------+-------------+------+-----+---------+-------+
3 rows in set (0.01 sec)
mysql> desc tb_member2;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id | bigint(20) | NO | PRI | NULL | |
| name | varchar(20) | YES | | NULL | |
| sex | tinyint(4) | NO | | 0 | |
+-------+-------------+------+-----+---------+-------+
3 rows in set (0.00 sec)
参数:INSERT_METHOD
-
INSERT_METHOD = NO :表示该表不能做任何写入操作只作为查询使用
-
INSERT_METHOD = LAST:表示插入到最后的一张表里面
-
INSERT_METHOD = first:表示插入到第一张表里面。
3)将数据分到两个表中
mysql> insert into tb_member1(id,name,sex) select id,name,sex from member where id%2=0;
Query OK, 8 rows affected (0.00 sec)
Records: 8 Duplicates: 0 Warnings: 0
mysql> insert into tb_member2(id,name,sex) select id,name,sex from member where id%2=1;
Query OK, 8 rows affected (0.00 sec)
Records: 8 Duplicates: 0 Warnings: 0
4)查看主表和子表的数据
#主表
mysql> select * from member;
+----+------+-----+
| id | name | sex |
+----+------+-----+
| 1 | tom1 | 1 |
| 2 | tom1 | 1 |
| 3 | tom1 | 1 |
| 4 | tom1 | 1 |
| 5 | tom1 | 1 |
| 6 | tom1 | 1 |
| 7 | tom1 | 1 |
| 8 | tom1 | 1 |
| 9 | tom1 | 1 |
| 10 | tom1 | 1 |
| 11 | tom1 | 1 |
| 12 | tom1 | 1 |
| 13 | tom1 | 1 |
| 14 | tom1 | 1 |
| 15 | tom1 | 1 |
| 16 | tom1 | 1 |
+----+------+-----+
16 rows in set (0.00 sec)
#第一个子表
mysql> select * from tb_member1;
+----+------+-----+
| id | name | sex |
+----+------+-----+
| 2 | tom1 | 1 |
| 4 | tom1 | 1 |
| 6 | tom1 | 1 |
| 8 | tom1 | 1 |
| 10 | tom1 | 1 |
| 12 | tom1 | 1 |
| 14 | tom1 | 1 |
| 16 | tom1 | 1 |
+----+------+-----+
8 rows in set (0.00 sec)
#第二个子表
mysql> select * from tb_member2;
+----+------+-----+
| id | name | sex |
+----+------+-----+
| 1 | tom1 | 1 |
| 3 | tom1 | 1 |
| 5 | tom1 | 1 |
| 7 | tom1 | 1 |
| 9 | tom1 | 1 |
| 11 | tom1 | 1 |
| 13 | tom1 | 1 |
| 15 | tom1 | 1 |
+----+------+-----+
8 rows in set (0.00 sec)
PS:总表只是一个外壳,存取数据发生在一个一个的子表里面。 每个子表都有自已独立的相关表文件,而主表只是一个壳,并没有完整的相关表文件!!!
上面三个表对应的本地文件如下:
[root@mysql ~]# ll /usr/local/mysql/data/test/tb_member*
-rw-r----- 1 mysql mysql 8614 1月 19 15:37 /usr/local/mysql/data/test/tb_member1.frm
-rw-r----- 1 mysql mysql 160 1月 19 15:49 /usr/local/mysql/data/test/tb_member1.MYD
-rw-r----- 1 mysql mysql 2048 1月 19 15:49 /usr/local/mysql/data/test/tb_member1.MYI
-rw-r----- 1 mysql mysql 8614 1月 19 15:38 /usr/local/mysql/data/test/tb_member2.frm
-rw-r----- 1 mysql mysql 160 1月 19 15:49 /usr/local/mysql/data/test/tb_member2.MYD
-rw-r----- 1 mysql mysql 2048 1月 19 15:49 /usr/local/mysql/data/test/tb_member2.MYI
-rw-r----- 1 mysql mysql 8614 1月 19 15:42 /usr/local/mysql/data/test/tb_member3.frm
-rw-r----- 1 mysql mysql 0 1月 19 15:42 /usr/local/mysql/data/test/tb_member3.MYD
-rw-r----- 1 mysql mysql 1024 1月 19 15:42 /usr/local/mysql/data/test/tb_member3.MYI
-rw-r----- 1 mysql mysql 8614 1月 19 15:42 /usr/local/mysql/data/test/tb_member.frm
-rw-r----- 1 mysql mysql 42 1月 19 15:42 /usr/local/mysql/data/test/tb_member.MRG
扩展
1.验证参数 ‘INSERT_METHOD’作用
#插入数据前查询
mysql> select count(*) from tb_member;
+----------+
| count(*) |
+----------+
| 18 |
+----------+
1 row in set (0.00 sec)
mysql> select count(*) from tb_member1;
+----------+
| count(*) |
+----------+
| 8 |
+----------+
1 row in set (0.00 sec)
mysql> select count(*) from tb_member2;
+----------+
| count(*) |
+----------+
| 10 |
+----------+
1 row in set (0.00 sec)
#对主表插入数据
mysql> insert into tb_member values(32729,'tom2',0),(31770,'tom3',0);
Query OK, 2 rows affected (0.00 sec)
Records: 2 Duplicates: 0 Warnings: 0
#插入数据后查询
mysql> select count(*) from tb_member;
+----------+
| count(*) |
+----------+
| 20 |
+----------+
1 row in set (0.00 sec)
mysql> select count(*) from tb_member1;
+----------+
| count(*) |
+----------+
| 8 |
+----------+
1 row in set (0.00 sec)
mysql> select count(*) from tb_member2;
+----------+
| count(*) |
+----------+
| 12 |
+----------+
1 row in set (0.00 sec)
**PS:**可以看出,新增的两条数据都插入在了第二张表中,因为在创建主表的时候,指定的“insert_method”是last,也就是所有插入数据的操作都是对最后一张表里进行的。
2.修改插入方式
1)修改为first
#修改
mysql> alter table tb_member insert_method=first;
Query OK, 0 rows affected (0.10 sec)
Records: 0 Duplicates: 0 Warnings: 0
#插入数据
mysql> insert into tb_member values(1000,'a1',1),(1001,'a2',1),(1002,'a3',1);
Query OK, 3 rows affected (0.00 sec)
Records: 3 Duplicates: 0 Warnings: 0
#查看插入后主表和子表
mysql> select count(*) from tb_member;
+----------+
| count(*) |
+----------+
| 23 |
+----------+
1 row in set (0.00 sec)
mysql> select count(*) from tb_member1;
+----------+
| count(*) |
+----------+
| 11 |
+----------+
1 row in set (0.00 sec)
mysql> select count(*) from tb_member2;
+----------+
| count(*) |
+----------+
| 12 |
+----------+
1 row in set (0.01 sec)
2)修改为no
mysql> insert into tb_member values(10004,'a5',1);
ERROR 1036 (HY000): Table 'tb_member' is read only
#表'tb_member'是只读的
二、分区
1.什么是分区
分区和分表相似,都是按照规则分解表。不同在于分表将大表分解为若干个独立的实体表,而分区是将数据分段划分在多个位置存放,分区后,表还是一张表,但数据散列到多个位置了。app读写的时候操作的还是表名字,db自动去组织分区的数据。
分区主要有以下两种形式:
- 水平分区:这种形式分区是对表的行进行分区,所有在表中定义的列在每个数据集中都能找到,所以表的特性依然得以保持。举个简单例子:一个包含十年发票记录的表可以被分区为十个不同的分区,每个分区包含的是其中一年的记录;
- 垂直分区:这种分区方式一般来说是通过对表的垂直划分来减少目标表的宽度,使某些特定的列被划分到特定的分区,每个分区都包含了其中的列所对应的行。举个简单例子:一个包含了大text和BLOB列的表,这些text和BLOB列又不经常被访问,这时候就要把这些不经常使用的text和BLOB了划分到另一个分区,在保证它们数据相关性的同时还能提高访问速度。
2.查看当前数据库是否支持分区
MySQL 5.6之前,使用下面的参数查看当前配置是否支持分区(如果为yes则表示支持分区):
mysql> SHOW VARIABLES LIKE '%partition%';
+-----------------------+---------------+
|Variable_name | Value |
+-----------------------+---------------+
| have_partition_engine | YES |
+-----------------------+------------------+
在5.6及以后采用以下方式查看:
#看到partition是ACTIVE的,表示支持分区
mysql> show plugins;
+----------------------------+----------+--------------------+---------+---------+
| Name | Status | Type | Library | License |
+----------------------------+----------+--------------------+---------+---------+
......
| FEDERATED | DISABLED | STORAGE ENGINE | NULL | GPL |
| partition | ACTIVE | STORAGE ENGINE | NULL | GPL |
| ngram | ACTIVE | FTPARSER | NULL | GPL |
+----------------------------+----------+--------------------+---------+---------+
44 rows in set (0.00 sec)
3.Range分区方式
**PS:**RANGE分区 基于属于一个给定连续区间的列值,把多行分配给分区。这些区间要连续且不能相互重叠,使用VALUES LESS THAN操作符来进行定义
mysql> create database test2;
Query OK, 1 row affected (0.00 sec)
mysql> use test2;
Database changed
mysql> create table user(
-> id int not null auto_increment,
-> name varchar(30) not null default '',
-> sex int(1) not null default '0',
-> primary key(id)
-> )default charset=utf8 auto_increment=1
-> partition by range(id)(
-> partition p0 values less than (3),
-> partition p1 values less than (6),
-> partition p2 values less than (9),
-> partition p3 values less than (12),
-> partition p4 values less than maxvalue );
Query OK, 0 rows affected (0.11 sec)
PS:在上面创建的表中,当id列的值小于3将会插入到p0分区,大于3小于6的记录将会插入到p1分区,以此类推,所有id值大于12的记录都会插入到p4分区
1.插入一些数据
PS:利用存储过程插入数据
mysql> delimiter !!
mysql> create procedure user()
-> begin
-> declare n int;
-> declare summary int;
-> set n = 0;
-> while n <= 20
-> do
-> insert into test2.user(name,sex) values('tom',0);
-> set n = n + 1;
-> end while;
-> end !!
Query OK, 0 rows affected (0.00 sec)
mysql> delimiter ;
mysql> call user();
Query OK, 1 row affected (0.02 sec)
mysql> select * from user;
+----+------+-----+
| id | name | sex |
+----+------+-----+
| 1 | tom | 0 |
| 2 | tom | 0 |
| 3 | tom | 0 |
| 4 | tom | 0 |
| 5 | tom | 0 |
| 6 | tom | 0 |
| 7 | tom | 0 |
| 8 | tom | 0 |
| 9 | tom | 0 |
| 10 | tom | 0 |
| 11 | tom | 0 |
| 12 | tom | 0 |
| 13 | tom | 0 |
| 14 | tom | 0 |
| 15 | tom | 0 |
| 16 | tom | 0 |
| 17 | tom | 0 |
| 18 | tom | 0 |
| 19 | tom | 0 |
| 20 | tom | 0 |
| 21 | tom | 0 |
+----+------+-----+
21 rows in set (0.00 sec)
2.到存放数据库表文件的地方看一下
[root@mysql ~]# ll /usr/local/mysql/data/test2/user*
-rw-r----- 1 mysql mysql 8614 1月 19 16:54 /usr/local/mysql/data/test2/user.frm
-rw-r----- 1 mysql mysql 98304 1月 19 17:00 /usr/local/mysql/data/test2/user#P#p0.ibd
-rw-r----- 1 mysql mysql 98304 1月 19 17:00 /usr/local/mysql/data/test2/user#P#p1.ibd
-rw-r----- 1 mysql mysql 98304 1月 19 17:00 /usr/local/mysql/data/test2/user#P#p2.ibd
-rw-r----- 1 mysql mysql 98304 1月 19 17:00 /usr/local/mysql/data/test2/user#P#p3.ibd
-rw-r----- 1 mysql mysql 98304 1月 19 17:00 /usr/local/mysql/data/test2/user#P#p4.ibd
# 给查询结果指定列名。
mysql> select count(id) as count from user;
+-------+
| count |
+-------+
| 21 |
+-------+
1 row in set (0.00 sec)
3.从information_schema系统库中的partitions表中查看分区信息
select * from information_schema.partitions where table_schema='test2' and table_name='user'\G;
*************************** 1. row ***************************
TABLE_CATALOG: def
TABLE_SCHEMA: test2 #库名
TABLE_NAME: user #表名
PARTITION_NAME: p0 #分区名
SUBPARTITION_NAME: NULL
PARTITION_ORDINAL_POSITION: 1 #分区位置,1表示第一个分区
SUBPARTITION_ORDINAL_POSITION: NULL
PARTITION_METHOD: RANGE #分区方式为range
SUBPARTITION_METHOD: NULL
PARTITION_EXPRESSION: id #以id列进行分区
SUBPARTITION_EXPRESSION: NULL
PARTITION_DESCRIPTION: 3 #分区范围是3
TABLE_ROWS: 2 #该分区中有两行数据
AVG_ROW_LENGTH: 8192
DATA_LENGTH: 16384
MAX_DATA_LENGTH: NULL
INDEX_LENGTH: 0
DATA_FREE: 0
CREATE_TIME: 2021-01-19 16:54:40
UPDATE_TIME: 2021-01-19 17:00:22
CHECK_TIME: NULL
CHECKSUM: NULL
PARTITION_COMMENT:
NODEGROUP: default
TABLESPACE_NAME: NULL
......
4.从某个分区中查询数据
mysql> select * from user partition(p0);
+----+------+-----+
| id | name | sex |
+----+------+-----+
| 1 | tom | 0 |
| 2 | tom | 0 |
+----+------+-----+
2 rows in set (0.00 sec)
5.添加分区及合并分区
PS:需要先合并分区再新增分区
在创建表的时候,指定的最后一个分区range是maxvalue,所以是无法直接增加分区的
mysql> alter table user add partition (partition p5 values less than(19));
ERROR 1481 (HY000): MAXVALUE can only be used in last partition definition
#MAXVALUE只能在最后一个分区定义中使用
但也不可以将最后定义了maxvalue的分区直接删除,因为删除分区的话,分区中的数据也会丢失,所以,如果需要新增分区的正确做法,应该是先合并分区,再新增分区,这样才可以保证数据的完整性
#将最后一个分区分为两个分区,一个是自己所需要的分区,最后一个分区还是maxvalue(也必须是maxvalue),这样就完成了添加分区
mysql> alter table user reorganize partition p4 into (partition p03 values less than(115),partition p04 values less than maxvalue);
Query OK, 0 rows affected (0.25 sec)
Records: 0 Duplicates: 0 Warnings: 0
#查看本地文件就会出现按重新更改的分区
[root@mysql ~]# ll /usr/local/mysql/data/test2/user*
-rw-r----- 1 mysql mysql 8614 1月 19 17:15 /usr/local/mysql/data/test2/user.frm
-rw-r----- 1 mysql mysql 98304 1月 19 17:15 /usr/local/mysql/data/test2/user#P#p03.ibd
-rw-r----- 1 mysql mysql 98304 1月 19 17:15 /usr/local/mysql/data/test2/user#P#p04.ibd
-rw-r----- 1 mysql mysql 98304 1月 19 17:00 /usr/local/mysql/data/test2/user#P#p0.ibd
-rw-r----- 1 mysql mysql 98304 1月 19 17:00 /usr/local/mysql/data/test2/user#P#p1.ibd
-rw-r----- 1 mysql mysql 98304 1月 19 17:00 /usr/local/mysql/data/test2/user#P#p2.ibd
-rw-r----- 1 mysql mysql 98304 1月 19 17:00 /usr/local/mysql/data/test2/user#P#p3.ibd
#查看新分区的数据
mysql> select * from user partition(p03);
+----+------+-----+
| id | name | sex |
+----+------+-----+
| 12 | tom | 0 |
| 13 | tom | 0 |
| 14 | tom | 0 |
+----+------+-----+
3 rows in set (0.00 sec)
mysql> select * from user partition(p04);
+----+------+-----+
| id | name | sex |
+----+------+-----+
| 15 | tom | 0 |
| 16 | tom | 0 |
| 17 | tom | 0 |
| 18 | tom | 0 |
| 19 | tom | 0 |
| 20 | tom | 0 |
| 21 | tom | 0 |
+----+------+-----+
7 rows in set (0.00 sec)
合并分区
mysql> alter table user reorganize partition p0,p1,p2,p3 into (partition p02 values less than(12));
Query OK, 0 rows affected (0.32 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> select * from user partition(p02);
+----+------+-----+
| id | name | sex |
+----+------+-----+
| 1 | tom | 0 |
| 2 | tom | 0 |
| 3 | tom | 0 |
| 4 | tom | 0 |
| 5 | tom | 0 |
| 6 | tom | 0 |
| 7 | tom | 0 |
| 8 | tom | 0 |
| 9 | tom | 0 |
| 10 | tom | 0 |
| 11 | tom | 0 |
+----+------+-----+
11 rows in set (0.00 sec)
#查看合并分区后的本地文件
[root@mysql ~]# ll /usr/local/mysql/data/test2/user*
-rw-r----- 1 mysql mysql 8614 1月 19 17:18 /usr/local/mysql/data/test2/user.frm
-rw-r----- 1 mysql mysql 98304 1月 19 17:18 /usr/local/mysql/data/test2/user#P#p02.ibd
-rw-r----- 1 mysql mysql 98304 1月 19 17:15 /usr/local/mysql/data/test2/user#P#p03.ibd
-rw-r----- 1 mysql mysql 98304 1月 19 17:15 /usr/local/mysql/data/test2/user#P#p04.ibd
参数:reorganize
- 意思为整理或重新定制,它不光可以合并也可以做拆分!!!
6.删除分区
#删除分区p02
mysql> alter table user drop partition p02;
Query OK, 0 rows affected (0.11 sec)
Records: 0 Duplicates: 0 Warnings: 0
#一旦删除分区之后,该分区中的数据一并都会删除包括本地文件!!!切记误操作!!!
mysql> select * from user partition(p02);
ERROR 1735 (HY000): Unknown partition 'p02' in table 'user'
[root@mysql ~]# ll /usr/local/mysql/data/test2/user*
-rw-r----- 1 mysql mysql 8614 1月 19 17:21 /usr/local/mysql/data/test2/user.frm
-rw-r----- 1 mysql mysql 98304 1月 19 17:15 /usr/local/mysql/data/test2/user#P#p03.ibd
-rw-r----- 1 mysql mysql 98304 1月 19 17:15 /usr/local/mysql/data/test2/user#P#p04.ibd
4.List分区方式
LIST分区类似于按RANGE分区,区别在于LIST分区是基于列值匹配一个离散值集合中的某个值来进行选择。LIST分区通过使用“PARTITION BY LIST(expr)”来实现,其中“expr” 是某列值或一个基于某个列值、并返回一个整数值的表达式,然后通过“VALUES IN (value_list)”的方式来定义每个分区,其中“value_list”是一个通过逗号分隔的整数列表。
1.创建分区
mysql> create database list;
Query OK, 1 row affected (0.00 sec)
mysql> use list;
Database changed
mysql> create table employees (
-> 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 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)
-> );
Query OK, 0 rows affected (0.15 sec)
2.插入数据
- 插入列值不在分区值列表中的一行时,那么“INSERT”查询将失败并报错。
mysql> INSERT INTO employees VALUES(224, 'Linus', 'Torvalds', '2002-05-01', '2004-10-12', 42, 21);
ERROR 1526 (HY000): Table has no partition for value 21
#报错为表没有分区值21!!!
- 列值符合分区的数值则可以插入。
mysql> INSERT INTO employees VALUES(224, 'Linus', 'Torvalds', '2002-05-01', '2004-10-12', 42, 19);
Query OK, 1 row affected (0.00 sec)
mysql> select * from employees partition(pEast);
+-----+-------+----------+------------+------------+----------+----------+
| id | fname | lname | hired | separated | job_code | store_id |
+-----+-------+----------+------------+------------+----------+----------+
| 224 | Linus | Torvalds | 2002-05-01 | 2004-10-12 | 42 | 19 |
+-----+-------+----------+------------+------------+----------+----------+
1 row in set (0.00 sec)
总结:
LIST分区没有类似如**“VALUES LESS THAN MAXVALUE”**这样的包含其他值在内的定义,只能将要匹配的任何值都必须在值列表中找到。
5.Hash分区方式
HASH分区模式允许DBA通过对表的一个或多个列的Hash Key进行计算,最后通过这个Hash码不同数值对应的数据区域进行分区。 hash分区的目的是将数据均匀的分布到预先定义的各个分区中,保证各分区的数据量大致一致。
1.分区比较
-
**range和list:**必须明确指定一个给定的列值或列值集合应该保存在哪个分区中。
-
**HASH:**MYSQL自动完成这些工作,用户所要定一个列值或者表达式,以及指定被分区的表将要被分割成的分区数量。
2.hash分区操作
mysql> create table t_hash(
-> a int (11),
-> b datetime
-> ) partition by hash (year(b)) partitions 4;
Query OK, 0 rows affected (0.02 sec)
#插入数据
mysql> insert into t_hash values (1,'2010-04-01');
Query OK, 1 row affected (0.01 sec)
#根据求模可以查看出插入的数据在哪个分区中!!!
mysql> select mod(year('2010-04-01'),4);
+---------------------------+
| mod(year('2010-04-01'),4) |
+---------------------------+
| 2 | #返回值为多少就是在那个分区中
+---------------------------+
1 row in set (0.00 sec)
mysql> select * from t_hash partition(p2);
+------+---------------------+
| a | b |
+------+---------------------+
| 1 | 2010-04-01 00:00:00 |
+------+---------------------+
1 row in set (0.00 sec)
mysql> insert into t_hash values (2,'2021-1-21');
Query OK, 1 row affected (0.00 sec)
mysql> select mod(year('2021-1-21'),4);
+--------------------------+
| mod(year('2021-1-21'),4) |
+--------------------------+
| 1 | #返回值为多少就是在那个分区中
+--------------------------+
1 row in set (0.00 sec)
mysql> select * from t_hash partition(p1);
+------+---------------------+
| a | b |
+------+---------------------+
| 2 | 2021-01-21 00:00:00 |
+------+---------------------+
1 row in set (0.00 sec)
#这样在information_schema系统库就可以查看到那个分区中有数据了!!!!
mysql> select * from information_schema.partitions where table_schema='list' and table_name='t_hash'\G
*************************** 1. row ***************************
TABLE_CATALOG: def
TABLE_SCHEMA: list
TABLE_NAME: t_hash
PARTITION_NAME: p0
SUBPARTITION_NAME: NULL
PARTITION_ORDINAL_POSITION: 1
SUBPARTITION_ORDINAL_POSITION: NULL
PARTITION_METHOD: HASH
SUBPARTITION_METHOD: NULL
PARTITION_EXPRESSION: year(b)
SUBPARTITION_EXPRESSION: NULL
PARTITION_DESCRIPTION: NULL
TABLE_ROWS: 1
AVG_ROW_LENGTH: 16384
DATA_LENGTH: 16384
MAX_DATA_LENGTH: NULL
INDEX_LENGTH: 0
DATA_FREE: 0
CREATE_TIME: 2021-01-21 15:50:12
UPDATE_TIME: 2021-01-21 16:02:11
CHECK_TIME: NULL
CHECKSUM: NULL
PARTITION_COMMENT:
NODEGROUP: default
TABLESPACE_NAME: NULL
*************************** 2. row ***************************
TABLE_CATALOG: def
TABLE_SCHEMA: list
TABLE_NAME: t_hash
PARTITION_NAME: p1
SUBPARTITION_NAME: NULL
PARTITION_ORDINAL_POSITION: 2
SUBPARTITION_ORDINAL_POSITION: NULL
PARTITION_METHOD: HASH
SUBPARTITION_METHOD: NULL
PARTITION_EXPRESSION: year(b)
SUBPARTITION_EXPRESSION: NULL
PARTITION_DESCRIPTION: NULL
TABLE_ROWS: 1
AVG_ROW_LENGTH: 16384
DATA_LENGTH: 16384
MAX_DATA_LENGTH: NULL
INDEX_LENGTH: 0
DATA_FREE: 0
CREATE_TIME: 2021-01-21 15:50:12
UPDATE_TIME: 2021-01-21 16:17:19
CHECK_TIME: NULL
CHECKSUM: NULL
PARTITION_COMMENT:
NODEGROUP: default
TABLESPACE_NAME: NULL
*************************** 3. row ***************************
TABLE_CATALOG: def
TABLE_SCHEMA: list
TABLE_NAME: t_hash
PARTITION_NAME: p2
SUBPARTITION_NAME: NULL
PARTITION_ORDINAL_POSITION: 3
SUBPARTITION_ORDINAL_POSITION: NULL
PARTITION_METHOD: HASH
SUBPARTITION_METHOD: NULL
PARTITION_EXPRESSION: year(b)
SUBPARTITION_EXPRESSION: NULL
PARTITION_DESCRIPTION: NULL
TABLE_ROWS: 1
AVG_ROW_LENGTH: 16384
DATA_LENGTH: 16384
MAX_DATA_LENGTH: NULL
INDEX_LENGTH: 0
DATA_FREE: 0
CREATE_TIME: 2021-01-21 15:50:12
UPDATE_TIME: 2021-01-21 15:51:52
CHECK_TIME: NULL
CHECKSUM: NULL
PARTITION_COMMENT:
NODEGROUP: default
TABLESPACE_NAME: NULL
*************************** 4. row ***************************
TABLE_CATALOG: def
TABLE_SCHEMA: list
TABLE_NAME: t_hash
PARTITION_NAME: p3
SUBPARTITION_NAME: NULL
PARTITION_ORDINAL_POSITION: 4
SUBPARTITION_ORDINAL_POSITION: NULL
PARTITION_METHOD: HASH
SUBPARTITION_METHOD: NULL
PARTITION_EXPRESSION: year(b)
SUBPARTITION_EXPRESSION: NULL
PARTITION_DESCRIPTION: NULL
TABLE_ROWS: 0
AVG_ROW_LENGTH: 0
DATA_LENGTH: 16384
MAX_DATA_LENGTH: NULL
INDEX_LENGTH: 0
DATA_FREE: 0
CREATE_TIME: 2021-01-21 15:50:12
UPDATE_TIME: NULL
CHECK_TIME: NULL
CHECKSUM: NULL
PARTITION_COMMENT:
NODEGROUP: default
TABLESPACE_NAME: NULL
4 rows in set (0.00 sec)
3.增加分区
#合并分区
mysql> alter table t_hash coalesce partition 3;
Query OK, 0 rows affected (0.13 sec)
Records: 0 Duplicates: 0 Warnings: 0
#增加分区
mysql> alter table t_hash add partition partitions 4;
Query OK, 0 rows affected (0.12 sec)
Records: 0 Duplicates: 0 Warnings: 0
#到目前已经变为5个分区了!!!!
6.Key分区方式
key分区和hash分区相似,不同在于hash分区是用户自定义函数进行分区,key分区使用mysql数据库提供的函数进行分区,NDB cluster使用MD5函数来分区,对于其他存储引擎mysql使用内部的hash函数。
mysql> create table t_key (
-> a int (11),
-> b datetime
-> ) partition by key(b) partitions 4;
Query OK, 0 rows affected (0.11 sec)
key分区的增加分区方法和hash分区的方式一样!!!
7.Columns分区方式
Columns分区 mysql-5.5开始支持COLUMNS分区,可视为RANGE和LIST分区的进化,COLUMNS分区可以直接使用非整形数据进行分区,COLUMNS可以使用多个列进行分区。
COLUMNS分区支持以下数据类型
类型 | 支持 | 不支持 |
---|---|---|
所有整形 | NT SMALLINT、TINYINT、BIGINT | FLOAT、DECIMAL |
日期类型 | DATE、DATETIME | 其余日期类型不支持 |
字符串类型 | CHAR、VARCHAR、BINARY、VARBINARY | BLOB、TEXT |
以RANGE COLUMNS分区举例
1.日期字段区分
#创建分区
mysql> CREATE TABLE members (
-> id INT,
-> joined DATE NOT NULL
-> )
-> PARTITION BY RANGE COLUMNS(joined) (
-> PARTITION a VALUES LESS THAN ('1960-01-01'),
-> PARTITION b VALUES LESS THAN ('1970-01-01'),
-> PARTITION c VALUES LESS THAN ('1980-01-01'),
-> PARTITION d VALUES LESS THAN ('1990-01-01'),
-> PARTITION e VALUES LESS THAN MAXVALUE
-> );
Query OK, 0 rows affected (0.01 sec)
#插入数据
mysql> insert into members(id,joined) values(1,'1950-01-01'),(1,'1960-01-01'),(1,'1980-01-01'),(1,'1990-01-01');
Query OK, 4 rows affected (0.00 sec)
Records: 4 Duplicates: 0 Warnings: 0
#查看数据分区分布
mysql> mysql> select * from information_schema.partitions where table_schema='list' le_name='members'\G
2.多个字段组合分区
#创建分区
mysql> CREATE TABLE rcx (
-> a INT,
-> b INT
-> )
-> PARTITION BY RANGE COLUMNS(a,b) (
-> PARTITION p0 VALUES LESS THAN (5,10),
-> PARTITION p1 VALUES LESS THAN (10,20),
-> PARTITION p2 VALUES LESS THAN (15,30),
-> PARTITION p3 VALUES LESS THAN (MAXVALUE,MAXVALUE)
-> );
Query OK, 0 rows affected (0.06 sec)
#插入数据
mysql> insert into rcx(a,b)values(1,20),(10,15),(10,30),(40,50);
Query OK, 3 rows affected (0.00 sec)
Records: 3 Duplicates: 0 Warnings: 0
#分析:
第一组值:(1,20);1<5所以不需要再比较20了,该记录属于p0分区。
第二组值:(10,15),10>5,10=10且15<20,所以该记录属于P1分区
第三组值:(10,30),10=10但是30>20,所以它不属于p1,它满足10<15所以它属于p2
第四组值:(40,50),40与50大于之前所有的分区值,所以为maxvalue p3分区
#查看分区
mysql> select * from rcx partition(p0);
+------+------+
| a | b |
+------+------+
| 1 | 20 |
+------+------+
1 row in set (0.01 sec)
mysql> select * from rcx partition(p1);
+------+------+
| a | b |
+------+------+
| 10 | 15 |
+------+------+
1 row in set (0.00 sec)
mysql> select * from rcx partition(p2);
+------+------+
| a | b |
+------+------+
| 10 | 30 |
+------+------+
1 row in set (0.00 sec)
mysql> select * from rcx partition(p3);
+------+------+
| a | b |
+------+------+
| 40 | 50 |
+------+------+
1 row in set (0.00 sec)
注意:
- RANGE COLUMN的多列分区第一列的分区值一定是顺序增长的,不能出现交叉值,第二列的值随便,例如以下分区就会报错。
- 由于分区P2的第一列比P1的第一列要小,所以报错,后面的分区第一列的值一定要比前面分区值要大,第二列没规定。
......
PARTITION BY RANGE COLUMNS(a,b) (
PARTITION p0 VALUES LESS THAN (5,10),
PARTITION p1 VALUES LESS THAN (10,20),
PARTITION p2 VALUES LESS THAN (8,30),
PARTITION p3 VALUES LESS THAN (MAXVALUE,MAXVALUE)
);
三、自定义分区位置
1.建表时,提前创建好存储目录,并授权给mysql
[root@mysql ~]# mkdir -p /data/area{1..3}
[root@mysql ~]# ls /data/
area1 area2 area3
[root@mysql ~]# chown -R mysql:mysql /data/
[root@mysql ~]# ls -l /data/
总用量 0
drwxr-xr-x 2 mysql mysql 6 1月 21 17:07 area1
drwxr-xr-x 2 mysql mysql 6 1月 21 17:07 area2
drwxr-xr-x 2 mysql mysql 6 1月 21 17:07 area3
2.创建表格
**PS:**使用mysql默认的存储引擎inodb时候,只需要指定data directory 就可以,因为inodb的数据和索引在一个文件中。但是创建表格时指定engine=myisam时,修改分区的存储位置,需要同时指定datadirectory和index directory。
mysql> create table user(
-> id int not null auto_increment,
-> name varchar(30) not null default '',
-> parimary key (id)
-> ) default charset=utf8 auto_increment=1
-> partition by range(id) (
-> partition p1 values less than (3) data directory '/data/area1',
-> partition p2 values less than (6) data directory '/data/area2',
-> partition p3 values less than (9) data directory '/data/area3');
Query OK, 0 rows affected (0.11 sec)
[root@mysql ~]# cd /data/
[root@mysql data]# tree
.
├── area1
│ └── list
│ └── user#P#p1.ibd
├── area2
│ └── list
│ └── user#P#p2.ibd
└── area3
└── list
└── user#P#p3.ibd
6 directories, 3 files
3.查看默认数据存储位置的文件
[root@mysql data]# cd /usr/local/mysql/data/list/ #数据库本地路径
[root@mysql list]# ll user*
-rw-r----- 1 mysql mysql 8586 1月 21 17:12 user.frm
-rw-r----- 1 mysql mysql 30 1月 21 17:12 user#P#p1.isl
-rw-r----- 1 mysql mysql 30 1月 21 17:12 user#P#p2.isl
-rw-r----- 1 mysql mysql 30 1月 21 17:12 user#P#p3.isl
四、分表和分区
1.分表和分区的区别:
1.实现方式
- mysql的分表是真正的分表,一张表分成很多表后,每一个小表都是完整的一张表,都对应三个文件,一个.MYD数据文件,.MYI索引文件,.frm表结构文件。
- 分区不一样,一张大表进行分区后,他还是一张表,不会变成二张表,但是他存放数据的区块变多了。
2.数据处理
- 分表后,数据都是存放在分表里,总表只是一个外壳,存取数据发生在一个一个的分表里面。
- 分区呢,不存在分表的概念,分区只不过把存放数据的文件分成了许多小块,分区后的表呢,还是一张表,数据处理还是由自己来完成。
3.提高性能
- 分表后,单表的并发能力提高了,磁盘I/O性能也提高了。并发能力为什么提高了呢,因为查寻一次所花的时间变短了,如果出现高并发的话,总表可以根据不同的查询,将并发压力分到不同的小表里面。
- mysql提出了分区的概念,主要是想突破磁盘I/O瓶颈,想提高磁盘的读写能力,来增加mysql性能。在这一点上,分区和分表的测重点不同,分表重点是存取数据时,如何提高mysql并发能力上;而分区呢,如何突破磁盘的读写能力,从而达到提高mysql性能的目的。
4.实现的难易度
- 分表的方法有很多,用merge来分表,是最简单的一种方式。这种方式跟分区难易度差不多,并且对程序代码来说可以做到透明的。如果是用其他分表方式就比分区麻烦了。
- 分区实现是比较简单的,建立分区表,根建平常的表没什么区别,并且对开代码端来说是透明的。
2.分表和分区有什么联系?
- 都能提高mysql的性高,在高并发状态下都有一个良好的表现。
- 分表和分区不矛盾,可以相互配合的,对于那些大访问量,并且表数据比较多的表,我们可以采取分表和分区结合的方式,访问量不大,但是表数据很多的表,我们可以采取分区的方式等。
- 分表技术是比较麻烦的,需要手动去创建子表,app服务端读写时候需要计算子表名。采用merge好一些,但也要创建子表和配置子表间的union关系。
- 表分区相对于分表,操作方便,不需要创建子表。
五、扩展
未分区表和分区表性能测试!!!
1.创建一个未分区的表
mysql> create table tab1(
-> c1 int,
-> c2 varchar(30),
-> c3 date
-> );
Query OK, 0 rows affected (0.01 sec)
mysql> CREATE TABLE tab2 (
-> c1 int,
-> c2 varchar(30) ,
-> c3 date )
-> PARTITION BY RANGE (year(c3)) ( PARTITION p0 VALUES LESS THAN (1995),
-> PARTITION p1 VALUES LESS THAN (1996) , PARTITION p2 VALUES LESS THAN (1997) ,
-> PARTITION p3 VALUES LESS THAN (1998) , PARTITION p4 VALUES LESS THAN (1999) ,
-> PARTITION p5 VALUES LESS THAN (2000) , PARTITION p6 VALUES LESS THAN (2001) ,
-> PARTITION p7 VALUES LESS THAN (2002) , PARTITION p8 VALUES LESS THAN (2003) ,
-> PARTITION p9 VALUES LESS THAN (2004) , PARTITION p10 VALUES LESS THAN (2010),
-> PARTITION p11 VALUES LESS THAN MAXVALUE );
Query OK, 0 rows affected (0.03 sec)
2.创建存储过程
mysql> delimiter @@
mysql> CREATE PROCEDURE ssss()
-> begin
-> declare v int default 0;
-> while v < 2000000
-> do
-> insert into tab1 values (v,'testing partitions',adddate('1995-01-01', (rand(v)*36520) mod 3652));
-> set v = v + 1;
-> end while;
-> end @@
mysql> delimiter ;
mysql> call ssss();
Query OK, 1 row affected (6 min 46.73 sec)
#此处时间较长请耐心等待!!!
#给表tab2加入跟tab1同样的数据!!!
mysql> insert into tab2 select * from tab1;
Query OK, 2000000 rows affected (11.78 sec)
Records: 2000000 Duplicates: 0 Warnings: 0
3.测试SQL性能
mysql> select count(*) from tab1 where c3 > '1995-01-01' and c3 < '1995-12-31';
+----------+
| count(*) |
+----------+
| 198799 |
+----------+
1 row in set (0.89 sec)
mysql> select count(*) from tab2 where c3 > '1995-01-01' and c3 < '1995-12-31';
+----------+
| count(*) |
+----------+
| 198799 |
+----------+
1 row in set (0.10 sec)
#结果表明分区表比未分区表的执行时间少很多。
#通过explain语句来分析执行情况
mysql> explain select count(*) from tab1 where c3 > '1995-01-01' and c3 <'1995-12-31'\G;
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: tab1
partitions: NULL
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 1861314
filtered: 11.11
Extra: Using where
1 row in set, 1 warning (0.00 sec)
mysql> explain select count(*) from tab2 where c3 > '1995-01-01' and c3 <'1995-12-31'\\G;
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: tab2
partitions: p1
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 199332
filtered: 11.11
Extra: Using where
1 row in set, 1 warning (0.00 sec)
4.创建索引
mysql> create index idx_of_c3 on tab1(c3);
Query OK, 0 rows affected (3.34 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> create index idx_of_c3 on tab2(c3);
Query OK, 0 rows affected (2.38 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> flush tables;
Query OK, 0 rows affected (0.00 sec)
5.再次测试SQL性能
mysql> select count(*) from tab1 where c3 > '1996-01-01' and c3 < '1996-12-31';
+----------+
| count(*) |
+----------+
| 199363 |
+----------+
1 row in set (0.10 sec)
mysql> select count(*) from tab2 where c3 > '1996-01-01' and c3 < '1996-12-31';
+----------+
| count(*) |
+----------+
| 199363 |
+----------+
1 row in set (0.09 sec)
**PS:**创建索引后分区表比未分区表相差不大(数据量越大差别会明显些)