MySQL 分表、分区 详解

分表和分区的目的
大表是指存储百万级乃至千万级条记录的表,这样的表过于庞大,导致数据库在查询和插入的时候耗时太长,性能低下,如果涉及联合查询的情况,性能会更加糟糕。分表和表分区的目的就是减少数据库的负担,提高数据库的效率,通常点来讲就是提高表的增删改查效率

分表和分区不矛盾,可以相互配合的,对于那些大访问量,并且表数据比较多的表,可以采取分表和分区结合的方式 (如果 merge 这种分表方式,不能和分区配合的话,可以用其他的分表试),访问量不大,但是表数据很多的表可以采取分区的方式等


分表
分表是将一个大表按照一定的规则分解成多张具有独立存储空间的实体表,可以称为子表。分表是逻辑上将一个大数据量的表分成多个,可以是水平分也可以是垂直分

根据分表技术对海量数据的优化方式目前有 2种方法 :
     <1> 垂直分割 : 把一个数据量很大的表,根据某个字段的属性或使用频繁程度分类拆分为多个表,或者把一个业务系统的库分到不同的实例上
                   优点 : 拆分后业务清晰,拆分规则明确,系统之间整合或者拓展容易,数据库维护简单
                   缺点 : 部分业务无法使用 JOIN,只能通过接口方式解决,提供系统能够复杂度,受每种业务不同的限制存在性能瓶颈,不容易数据扩展跟性能提高。另外,事务处理复杂,垂直切分后按照业务的分类将表分散到不同的库,会导致有些业务表过于庞大,存在单库读写与存储瓶颈
     <2> 水平分割 : 根据一列或者多列的值把数据行放到多个独立的表里,水平分表方式可以通过多个低配置主机整合起来,实现高性能
                   优点 : 拆分规则抽象好,JION操作基本可以数据库做,不存在单表大数据、高并发的性能瓶颈,应用端改造较少,提高系统的稳定性和负载能力
                   缺点 : 分片事务一致性难以解决,在 MyCAT2.0之前 MySQL5.7之前,还是数据弱XA。数据多次扩展难度维护量大,夸库JOIN性能差
逻辑分表 : 用户自己定义规则,将数据分配到不同的表或数据库中
物理分表 : 由数据库决定,和分区中4中主要分配方式相似


分区
分区是将表的一个数据文件拆分成多个,不同的数据拆分到不同的文件中。这样对于一个数据量非常大的表,有多个数据文件来进行存储,这样就提
高了数据库的 IO 性能。分区后,表面上还是一张表,但数据散列到多个位置根据数据量的大小,分区方式主要有 4 中 :
1> range : 主要用于时间列分区、值范围,行数据基于一个给定连续分区的列值放入分区,如销售类的表,可以根据年来分区存放销售记录
2> list : 面向离散的值,分区要指定的值,当插入指定的数据到指定分区表去,如指定某些值在特定分区里
3> hash : 基于用户定义的表达式的返回值来进行选择的分区,该表达式使用将要插入到表中的这些行的列值进行计算。这个函数可以包含 MySQL 中有效的、产生非负整数值的任何表达式
3> key : 类似于按 HASH分区,区别在于 KEY分区 只支持计算一列或多列,且 MySQL服务器提供其自身的哈希函数,必须有一列或多列包含整数值

注 : 在用 INSERT 插入多行数据的过程中遇到分区为定义的值,MyISAM、InnoDB 存储引擎的处理完全不同,MyISAM 一条不成功 之前的成功值会进入表中,InnoDB 只要一条不成功所有都不成功

1> Range : 主要用于时间列分区,如销售类的表,可以根据年来分区存放销售记录
                   适用场景 : 1> 当需要删除 "旧的" 数据时。如果使用上面最近的那个例子给出的分区方案,只需简单地使用 "ALTER TABLE employees DROP PARTITION p0;" 来删除所有在 1991 年前就已经停止工作的雇员相对应的所有行。对于有大量行的表,这比运行一个如 "DELETE FROM employees WHERE YEAR(separated) <=1990;" 这样的一个 DELETE 查询要有效得多
                                    2> 使用一个包含有日期或时间值,或包含有从一些其他级数开始增长的值的列
                                    3> 经常运行直接依赖于用于分割表的列的查询。例如,当执行一个如 "SELECT COUNT(*) FROM employees WHERE YEAR(separated) = 2000 GROUP BY store_id;" 这样的查询时,MySQL 可以很迅速地确定只有分区 p2 需要扫描,这是因为余下的分区不可能包含有符合该 WHERE 子句的任何记录
# 创建表分区
mysql> CREATE TABLE t_range(id INT PRIMARY KEY AUTO_INCREMENT,name VARCHAR(10)) PARTITION BY RANGE(id)(
     > partition p0 values less than (1000000),
     > partition p1 values less than (2000000),
     > partition p2 values less than (3000000),
     > partition p3 values less than (4000000),
     > partition p4 values less than maxvalue
     > );

# ll    ## 查看分区文件
总用量 294M
-rw-r-----. 1 mysql mysql 8.6K 3月  10 02:16 article2.frm
-rw-r-----. 1 mysql mysql  25M 3月  10 02:19 article2.ibd
-rw-r-----. 1 mysql mysql 8.6K 3月  10 01:32 article.frm
-rw-r-----. 1 mysql mysql  36M 3月  10 02:14 article.ibd
-rw-r-----. 1 mysql mysql   61 3月   8 21:42 db.opt
-rw-r-----. 1 mysql mysql 8.4K 3月  18 05:00 t_base.frm
-rw-r-----. 1 mysql mysql 232M 3月  18 05:02 t_base.ibd
-rw-r-----. 1 mysql mysql 8.4K 3月  18 05:02 t_range.frm
-rw-r-----. 1 mysql mysql  96K 3月  18 05:03 t_range#P#p0.ibd
-rw-r-----. 1 mysql mysql  96K 3月  18 05:03 t_range#P#p1.ibd
-rw-r-----. 1 mysql mysql  96K 3月  18 05:03 t_range#P#p2.ibd
-rw-r-----. 1 mysql mysql  96K 3月  18 05:03 t_range#P#p3.ibd
-rw-r-----. 1 mysql mysql  96K 3月  18 05:03 t_range#P#p4.ibd


# 通过文件的大小来查看数据插入的位置
mysql> INSERT INTO t_range(name) VALUES('a');
mysql> INSERT INTO t_range(name) SELECT name FROM t_range;

mysql> SELECT * FROM t_range LIMIT 0,10;
+----+------+
| id | name |
+----+------+
|  1 | a    |
|  2 | a    |
|  3 | a    |
|  4 | a    |
|  5 | a    |
|  6 | a    |
|  7 | a    |
|  8 | a    |
|  9 | a    |
| 10 | a    |
+----+------+
10 rows in set (0.01 sec)

# 删除第一个分区
mysql> ALTER TABLE t_range DROP PARTITION p0;
Query OK, 0 rows affected (0.06 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> SELECT * FROM t_range LIMIT 0,10;
+---------+------+
| id      | name |
+---------+------+
| 1000000 | a    |
| 1000001 | a    |
| 1000002 | a    |
| 1000003 | a    |
| 1000004 | a    |
| 1000005 | a    |
| 1000006 | a    |
| 1000007 | a    |
| 1000008 | a    |
| 1000009 | a    |
+---------+------+
10 rows in set (0.00 sec)
mysql> SHOW CREATE TABLE t_range;
...
| t_range | CREATE TABLE `t_range` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(10) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2097153 DEFAULT CHARSET=utf8
/*!50100 PARTITION BY RANGE (id)
(PARTITION p1 VALUES LESS THAN (2000000) ENGINE = InnoDB,
PARTITION p2 VALUES LESS THAN (3000000) ENGINE = InnoDB,
PARTITION p3 VALUES LESS THAN (4000000) ENGINE = InnoDB,
PARTITION p4 VALUES LESS THAN MAXVALUE ENGINE = InnoDB) */ |
...
1 row in set (0.01 sec)


# ll
总用量 341M
-rw-r-----. 1 mysql mysql 8.6K 3月  10 02:16 article2.frm
-rw-r-----. 1 mysql mysql  25M 3月  10 02:19 article2.ibd
-rw-r-----. 1 mysql mysql 8.6K 3月  10 01:32 article.frm
-rw-r-----. 1 mysql mysql  36M 3月  10 02:14 article.ibd
-rw-r-----. 1 mysql mysql   61 3月   8 21:42 db.opt
-rw-r-----. 1 mysql mysql 8.4K 3月  18 05:00 t_base.frm
-rw-r-----. 1 mysql mysql 232M 3月  18 05:02 t_base.ibd
-rw-r-----. 1 mysql mysql 8.4K 3月  18 05:09 t_range.frm
-rw-r-----. 1 mysql mysql  36M 3月  18 05:08 t_range#P#p1.ibd
-rw-r-----. 1 mysql mysql  11M 3月  18 05:08 t_range#P#p2.ibd
-rw-r-----. 1 mysql mysql  96K 3月  18 05:03 t_range#P#p3.ibd
-rw-r-----. 1 mysql mysql  96K 3月  18 05:03 t_range#P#p4.ibd

2> List : 面向离散的值,分区要指定的值,当插入指定的数据到指定分区表去,LIST() 所包含的列必须全部都是主键,和 Rang 区别在于 LIST 分区是基于列值匹配一个离散值集合中的某个值来进行选择
mysql> CREATE TABLE t_list(id INT AUTO_INCREMENT,province int,PRIMARY KEY(id,province)) PARTITION BY LIST(province)(
     > PARTITION p0 VALUES IN(1,2,3),
     > PARTITION p1 VALUES IN(4,5,6)
     > );

mysql> INSERT INTO t_list(province) VALUES(1),(2),(3),(4),(5),(6);
mysql> INSERT INTO t_list(province) SELECT province FROM t_list;

mysql> ll
总用量 341M
-rw-r-----. 1 mysql mysql 8.6K 3月  10 02:16 article2.frm
-rw-r-----. 1 mysql mysql  25M 3月  10 02:19 article2.ibd
-rw-r-----. 1 mysql mysql 8.6K 3月  10 01:32 article.frm
-rw-r-----. 1 mysql mysql  36M 3月  10 02:14 article.ibd
-rw-r-----. 1 mysql mysql   61 3月   8 21:42 db.opt
-rw-r-----. 1 mysql mysql 8.4K 3月  18 05:00 t_base.frm
-rw-r-----. 1 mysql mysql 232M 3月  18 05:02 t_base.ibd
-rw-r-----. 1 mysql mysql 8.4K 3月  18 06:09 t_list.frm
-rw-r-----. 1 mysql mysql  96K 3月  18 06:12 t_list#P#p0.ibd
-rw-r-----. 1 mysql mysql  96K 3月  18 06:12 t_list#P#p1.ibd
-rw-r-----. 1 mysql mysql 8.4K 3月  18 05:09 t_range.frm
-rw-r-----. 1 mysql mysql  36M 3月  18 05:08 t_range#P#p1.ibd
-rw-r-----. 1 mysql mysql  11M 3月  18 05:08 t_range#P#p2.ibd
-rw-r-----. 1 mysql mysql  96K 3月  18 05:03 t_range#P#p3.ibd
-rw-r-----. 1 mysql mysql  96K 3月  18 05:03 t_range#P#p4.ibd

mysql> ALTER TABLE t_list DROP PARTITION p0;
Query OK, 0 rows affected (0.01 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> SHOW CREATE TABLE t_list;
...
| t_list | CREATE TABLE `t_list` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `province` int(11) NOT NULL,
  PRIMARY KEY (`id`,`province`)
) ENGINE=InnoDB AUTO_INCREMENT=97 DEFAULT CHARSET=utf8
/*!50100 PARTITION BY LIST (province)
(PARTITION p1 VALUES IN (4,5,6) ENGINE = InnoDB) */ |
...
1 row in set (0.00 sec)

mysql> ll
总用量 341M
-rw-r-----. 1 mysql mysql 8.6K 3月  10 02:16 article2.frm
-rw-r-----. 1 mysql mysql  25M 3月  10 02:19 article2.ibd
-rw-r-----. 1 mysql mysql 8.6K 3月  10 01:32 article.frm
-rw-r-----. 1 mysql mysql  36M 3月  10 02:14 article.ibd
-rw-r-----. 1 mysql mysql   61 3月   8 21:42 db.opt
-rw-r-----. 1 mysql mysql 8.4K 3月  18 05:00 t_base.frm
-rw-r-----. 1 mysql mysql 232M 3月  18 05:02 t_base.ibd
-rw-r-----. 1 mysql mysql 8.4K 3月  18 06:16 t_list.frm
-rw-r-----. 1 mysql mysql  96K 3月  18 06:12 t_list#P#p1.ibd
-rw-r-----. 1 mysql mysql 8.4K 3月  18 05:09 t_range.frm
-rw-r-----. 1 mysql mysql  36M 3月  18 05:08 t_range#P#p1.ibd
-rw-r-----. 1 mysql mysql  11M 3月  18 05:08 t_range#P#p2.ibd
-rw-r-----. 1 mysql mysql  96K 3月  18 05:03 t_range#P#p3.ibd
-rw-r-----. 1 mysql mysql  96K 3月  18 05:03 t_range#P#p4.ibd

3> Hash : 如果表中存在主键或是唯一索引时分区列必须是唯一索引的一个组成部分,如果没有主键或唯一索引可以指定任何一个列为分区列。Hash 分区主要用来确保数据在预先确定数目的分区中平均分布。在 RANGE 和 LIST 分区中,必须明确指定一个给定的列值或列值集合应该保存在哪个分区中;而在 Hash 分区中, MySQL 自动完成这些工作
mysql> CREATE TABLE t_hash(id INT PRIMARY KEY AUTO_INCREMENT,name VARCHAR(10)) PARTITION BY HASH(id) PARTITIONS 4;
Query OK, 0 rows affected (0.02 sec)

mysql> ll
总用量 341M
-rw-r-----. 1 mysql mysql 8.6K 3月  10 02:16 article2.frm
-rw-r-----. 1 mysql mysql  25M 3月  10 02:19 article2.ibd
-rw-r-----. 1 mysql mysql 8.6K 3月  10 01:32 article.frm
-rw-r-----. 1 mysql mysql  36M 3月  10 02:14 article.ibd
-rw-r-----. 1 mysql mysql   61 3月   8 21:42 db.opt
-rw-r-----. 1 mysql mysql 8.4K 3月  18 05:00 t_base.frm
-rw-r-----. 1 mysql mysql 232M 3月  18 05:02 t_base.ibd
-rw-r-----. 1 mysql mysql 8.4K 3月  18 06:22 t_hash.frm
-rw-r-----. 1 mysql mysql  96K 3月  18 06:22 t_hash#P#p0.ibd
-rw-r-----. 1 mysql mysql  96K 3月  18 06:22 t_hash#P#p1.ibd
-rw-r-----. 1 mysql mysql  96K 3月  18 06:22 t_hash#P#p2.ibd
-rw-r-----. 1 mysql mysql  96K 3月  18 06:22 t_hash#P#p3.ibd
-rw-r-----. 1 mysql mysql 8.4K 3月  18 06:16 t_list.frm
-rw-r-----. 1 mysql mysql  96K 3月  18 06:12 t_list#P#p1.ibd
-rw-r-----. 1 mysql mysql 8.4K 3月  18 05:09 t_range.frm
-rw-r-----. 1 mysql mysql  36M 3月  18 05:08 t_range#P#p1.ibd
-rw-r-----. 1 mysql mysql  11M 3月  18 05:08 t_range#P#p2.ibd
-rw-r-----. 1 mysql mysql  96K 3月  18 05:03 t_range#P#p3.ibd
-rw-r-----. 1 mysql mysql  96K 3月  18 05:03 t_range#P#p4.ibd

# 按年份进行 Hash 计算, partition by hash(expr)  expr 可以是列或表达式
mysql> create table t_hash2(col1 int not null,col2 date not null,col3 int not null,col4 int not null,unique key (col1,col2)) partition by hash(year(col2))  partitions 4;
4> Key (使用的比较少)

分区注意点 :
1> 重新分区时,如果原分区里面存在 maxvalue 则新的分区里面也必须包含 maxvalue 否则就错误
2> 分区删除时,数据也同样会被删除 alter table p_range drop partition p0;
3> 如果 range 分区列表里面没有 maxvalue 则如有新数据大于现在分区 range 数据值那么这个数据是无法写入到数据库表的
4> 修改表名不需要删除分区后在进行更改,修改表名后分区存储 myd myi 对应也会自动更改。如果希望从所有分区删除所有的数据,但是又保留表的定义和表的分区模式,使用 TRUNCATE TABLE 命令。如果希望改变表的分区而又不丢失数据,使用 "ALTER TABLE … REORGANIZE PARTITION" 语句
5> 对表进行分区时,不论采用哪种分区方式如果表中存在主键那么主键必须在分区列中,表分区的局限性
6> list 方式分区没有类似于 range 那种 less than maxvalue 的写法,也就是说 list 分区表的所有数据都必须在分区字段的值列表集合中
7> 在 MySQL 5.1 版中,同一个分区表的所有分区必须使用同一个存储引擎;例如,不能对一个分区使用 MyISAM,而对另一个使用 InnoDB
8> 分区的名字是不区分大小写的, myp1 与 MYp1 是相同的


你可能感兴趣的:(MySQL)