MySQL表分区
Posted atwdy
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MySQL表分区相关的知识,希望对你有一定的参考价值。
学到这儿,边学边测,简要记录一下,以下代码都在mysql8.0下实测过。
关于分区以及为什么要分区
我们首先找到mysql的数据存储目录,可以通过语句show variables like '%datadir%';
查看,我本机的是"C:\\ProgramData\\MySQL\\MySQL Server 8.0\\Data",在该目录下,可以看到每个数据库对应着一个文件夹,对于没有分区的表,库中的每个表就对应着文件夹下的一个ibd文件
当一个表中的数据量太大时,会面临两个问题,一是对数据的操作会变慢,比如select、join、update、delete时,会对全表操作;二是不便于存储,可能会出现剩余磁盘空间存储不下这张表的情况。而分区就可以在一定程度上解决这两个问题。
简要的说,分区就是将表物理截断,但在逻辑上依然是一个整体,开发人员在数据操作时仍然是对这个整体大表进行操作,之后由数据库底层自己去寻找对应的分区进行操作,数据库底层寻找分区这个过程对开发人员来说是透明的,这样在数据操作时可以只对特定分区操作以提高效率,存储时也可以将不同分区的物理文件分开存放,下面是一个有3个分区(p1、p2、p3)的表(p_table)的实际存储
注:当过滤条件为分区的字段时才会自动寻找分区,否则还是全表扫描
水平分区的几种类型及demo
之所以特别说明一下是水平分区,是因为还有一种垂直分区的分区方式,二者一个横向切割一个纵向切割,(对比之下感觉水平分区类似于HBase中的segment,垂直分区类似于HBase中的region~),关于垂直分区先跳过,一是没找到多少相关的资料,二是感觉业务中用到的也不多,大多用的都是水平分区,有时间日后再补。
mysql中的水平分区包含下面几种:
1.range分区
range分区,顾名思义,就是按照范围进行分区,下面是创建一个range分区表:
drop table if exists `range_table`;
create table `range_table`(
`id` int,
`name` varchar(10)
)
partition by range(id)(
partition p1 values less than (10),
partition p2 values less than (20),
partition p3 values less than maxvalue
);
上面以id为分区字段,根据id大小划分为[-∞, 10),[10, 20),[20, +∞]三个区间,注意包前不包后,在数据插入时会自动根据id插入到各自分区
# 插入数据
insert into range_table (id, name) values (1,"梁静茹"),(10,"金泰妍"),(15,"王菲"),(50,"邵夷贝");
# 查看各个分区数据条数
select partition_name,table_rows from information_schema.partitions where table_name = 'range_table';
分别指定分区查看各个分区里面的数据,可以看到id为1的保存到了p1,id为10和15的保存到了p2,id为50的保存到了p3
select * from range_table partition (p1);
select * from range_table partition (p2);
select * from range_table partition (p3);
在进行select/update/delete时如果where后面的限制条件包含分区字段id时会自动去对应分区中查找,否则还是全表扫描。
explain select * from range_table where id = "1" and name = '梁静茹';
explain select * from range_table where name = '梁静茹';
range分区字段只支持整型,如果需要对时间日期这样的字段进行range分区,可以通过相关函数将类型转为整型再分区。
2.list分区
list就是枚举的意思,list分区就是在创建各分区时具体指定哪些值属于这些分区,下面是创建list分区表的代码:
drop table if exists `list_table`;
create table `list_table`(
`id` int,
`name` varchar(10)
)
partition by list(id)(
partition p1 values in (1),
partition p2 values in (10,15,50)
);
重新执行插入语句insert into list_table (id, name) values (1,"梁静茹"),(10,"金泰妍"),(15,"王菲"),(50,"邵夷贝");
,id为1的保存到了p1分区,id为10,15,20的保存到了p2分区,需要注意如果插入数据的 id 在各个分区所对应着的列表里面都没找到,则会报错。
list分区分区字段同样只能是int型。
3.hash分区
hash分区分为常规hash和线性hash,常规hash是在分区字段上基于分区个数的取模运算,根据余数分区。线性hash是对分区字段进行二次方运算,根据运算结果分区,所以hash分区同样要求分区字段为整型或者是可以返回整型结果的表达式。二者在建表时候的区别只是线性hash比常规hash多了个linear
(线性的)限定。
3.1.常规hash
常规hash分区建表:
drop table if exists `hash_table`;
create table `hash_table`(
`id` int,
`name` varchar(10)
)
partition by hash(id)
partitions 3;
hash分区不能指定分区名,会默认创建名为pn的分区,n从0开始自增。上面这段代码会创建p0,p1,p2三个分区,分区名可以通过下面的sql查看,
select partition_name from information_schema.PARTITIONS where table_schema = schema() and table_name = "hash_table";
上面说的常规hash就是基于分区数对分区字段进行取模求余操作,按照这种计算,插入下面的数据,
insert into hash_table (id, name) values (1,"梁静茹"),(10,"金泰妍"),(15,"王菲"),(50,"邵夷贝");
1 10 15 50 分别对3求余对应的结果 1 1 0 2,也就是上面4条数据应该分别被保存到p1, p1, p0, p2分区,对此进行验证:
select 'p0' as part, t.* from hash_table partition (p0) t
union
select 'p1' as part, t.* from hash_table partition (p1) t
union
select 'p2' as part, t.* from hash_table partition (p2) t;
这样当在查询的时候会采用相同的取模运算到对应分区下查找,比如查id为5的数据,就会去p2分区查找。
3.2.线性hash
线性hash在建表时只是比常规hash多了个linear
字段:
drop table if exists `hash_linear_table`;
create table `hash_linear_table`(
`id` int,
`name` varchar(10)
)
partition by linear hash(id)
partitions 3;
关于线性分区的具体计算规则可以参考官方文档:https://dev.mysql.com/doc/refman/8.0/en/partitioning-linear-hash.html,这里假设num是分区个数,value是某条记录的分区字段对应的值,N是最终经过计算得到的某个分区编号,则N的计算过程如下:
step1:V = power(2, ceil(log(2, num)))
step2:N = value & (V-1)
step3:if N>=num: N=N & (ceil(V/2) - 1)
按照上面步骤,将id为50的这条数据代入计算:
step1:V = power(2, ceil(log(2, num))) = power(2, ceil(log(2, 3))) = power(2, 2) = 4
step2:N = value & (V-1) = 50 & 3 = 110010 & 000011 = 000010 = 2
step3:N>=num? <=> 2>=3? False:N=2
即id为50的这条数据保存到p2分区,同理可以计算出id为10时N=2,id为1时N=1,id为15是N=1,验证一下计算结果:
select 'p0' as part, t.* from hash_linear_table partition (p0) t
union
select 'p1' as part, t.* from hash_linear_table partition (p1) t
union
select 'p2' as part, t.* from hash_linear_table partition (p2) t;
结果计算正确。
4.key分区
主要还是参考官方文档吧,https://dev.mysql.com/doc/refman/8.0/en/partitioning-key.html,里面主要说的是,key分区类似于hash分区,只不过分区列不再强制为整型,可以为除text和BLOB两种类型外的其它类型。key分区也有两种,常规key和线性key,常规key对分区字段采用的是MD5算法,线性key对分区字段采用的是二次方算法,参考hash分区中的线性hash,分区列选取的具体规则为:
- 当表中只有主键primary key或只有唯一键unique key时,分区列必须包含主键或唯一键中的部分或全部字段,不允许出现主键或唯一键中字段以外的其它字段
- 当表中主键和唯一键同时存在时,分区列为主键和唯一键公共字段的部分或全部
- 当表中主键唯一键都没有时:任意指定除text和BLOB类型外的其它字段,可以为1个或多个
分区列也可以缺省不指定,但必须要求表中存在主键或唯一键,优先以主键作为分区字段,没有主键时以唯一键作为分区字段,此时唯一键必须显示指定not null。
下面是常规key分区建表的一个demo,name为分区字段:
drop table if exists `key_table`;
create table `key_table`(
`id` int,
`name` varchar(10) not null,
unique `uk_name` (name)
)
partition by key()
partitions 3;
线性key分区的建表也只是多了一个linear
字段:
drop table if exists `key_table`;
create table `key_table`(
`id` int,
`name` varchar(10) not null,
unique `uk_name` (name)
)
partition by linear key()
partitions 3;
5.子分区(复合分区)
文档地址:https://dev.mysql.com/doc/refman/8.0/en/partitioning-subpartitions.html,里面有这么一段话,
说的是我们可以对采用range分区或者list分区的表,进行二次分区,二次分区只能为hash分区或者key分区。这种分区方式有两种建表写法,一种是指定子分区名,一种是不指定子分区名由系统默认。
不指定子分区名创建:
drop table if exists `subpart_table`;
create table `subpart_table`(
dt date
)
partition by range(year(dt))
subpartition by hash(month(dt))
subpartitions 2 (
partition p1 values less than (1990),
partition p2 values less than (2000),
partition p3 values less than maxvalue
);
通过select partition_name, subpartition_name from information_schema.partitions where table_schema = schema() and table_name = 'subpart_table';
查看下各个子分区:
物理上也被分成了单独的6个文件:
指定分区名创建,这种方式要求每个一级分区下的子分区数量必须一致,所有子分区的分区名不能重复:
drop table if exists `subpart_table`;
create table `subpart_table`(
dt date
)
partition by range(year(dt))
subpartition by hash(month(dt))
(
partition p1 values less than (1990)(
subpartition s1,
subpartition s2
),
partition p2 values less than (2000)(
subpartition s3,
subpartition s4
),
partition p3 values less than maxvalue(
subpartition s5,
subpartition s6
)
);
上表根据日期的年份进行一级分区,根据日期的月份二级分区,s1、s3、s5存偶数月,s2、s4、s6存奇数月,插入数据验证一下:
insert into subpart_table values('1989-01-01'), ('1989-02-01'),
('1995-01-01'), ('1989-02-01'),
('2022-01-01'), ('2022-02-01');
select 's1' as part, t.* from subpart_table partition (s1) t
union
select 's2' as part, t.* from subpart_table partition (s2) t
union
select 's3' as part, t.* from subpart_table partition (s3) t
union
select 's4' as part, t.* from subpart_table partition (s4) t
union
select 's5' as part, t.* from subpart_table partition (s5) t
union
select 's6' as part, t.* from subpart_table partition (s6) t;
6.columns分区
包含range columns和list columns两种,分区字段可以不为整型,可以有多个,感觉用得不多,附上文档地址,用到的时候再来学习吧,https://dev.mysql.com/doc/refman/8.0/en/partitioning-columns.html
mysql表分区使用及详细介绍
参考技术A一、分区概念
分区是将一个表分成多个区块进行操作和保存,从而降低每次操作的数据,提高性能。而对于应用来说则是透明的,从逻辑上看只有一张表,但在物理上这个表可能是由多个物理分区组成的,每个分区都是独立的对象,可以进行独立处理。
二、分区作用
1.可以逻辑数据分割,分割数据能够有多个不同的物理文件路径。
2.可以存储更多的数据,突破系统单个文件最大限制。
3.提升性能,提高每个分区的读写速度,提高分区范围查询的速度。
4.可以通过删除相关分区来快速删除数据
5.通过跨多个磁盘来分散数据查询,从而提高磁盘I/O的性能。
6.涉及到例如SUM()、COUNT()这样聚合函数的查询,可以很容易的进行并行处理。
7.可以备份和恢复独立的分区,这对大数据量很有好处。
三、分区能支持的引擎
MySQL支持大部分引擎创建分区,入MyISAM、InnoDB等;不支持MERGE和CSV等来创建分区。同一个分区表中的所有分区必须是同一个存储引擎。值得注意的是,在MySQL8版本中,MyISAM表引擎不支持分区。
四、确认MySQL支持分区
从MySQL5.1开始引入分区功能,可以如下方式查看是否支持:
老版本用:SHOW VARIABLES LIKE \'%partition%\';
新版本用:show plugins;
五、分区类型
1. RANGE分区:基于属于一个给定连续区间的列值,把多行分配给分区。
例如,可以将一个表通过年份划分成两个分区,2001 -2010年、2011-2020。
2. LIST分区:类似于RANGE分区,LIST是列值匹配一个离散值集合中的某个值来进行选择。
比如 根据字段 把值为1、3、5的放到一起,2、4、6的另外放到一起 等等...
3. HASH分区:基于用户定义的表达式的返回值来进行选择分区,该表达式使用将要插入到表中的这些行的列值来进行计算,这个函数必须产生非负整数值。
通过HASH运算来进行分区,分布的比较均匀
4. KEY分区:类似于按HASH分区,由MySQL服务器提供其自身的哈希函数。
按照KEY进行分区类似于按照HASH分区
六、分区创建注意事项
1. 如果表中存在primary key 或者 unique key 时,分区的列必须是paimary key或者unique key的一个组成部分,也就是说,分区函数的列只能从pk或者uk这些key中取子集
2. 如果表中不存在任何的paimary key或者unique key,则可以指定任何一个列作为分区列
3. 5.5版本前的RANGE、LIST、HASH分区要求分区键必须是int;MySQL5.5及以上,支持非整形的RANGE和LIST分区,即:range columns 和 list columns (可以用字符串来进行分区)。
七、分区命名
1. 分区的名字基本上遵循其他MySQL 标识符应当遵循的原则,例如用于表和数据库名字的标识符。应当注意的是, 分区的名字是不区分大小写的 。
2. 无论使用何种类型的分区,分区总是在创建时就自动的顺序编号,且从0开始记录。
八、 创建分区
1. RANGE分区:
CREATE TABLE `test01` (
`dayid` int(11) DEFAULT NULL,
`mac` varchar(32) NOT NULL DEFAULT \'\',
`dtype` varchar(50) NOT NULL DEFAULT \'\'
) ENGINE=InnoDB DEFAULT CHARSET=utf8
/*!50100 PARTITION BY LIST (dayid)
(PARTITION p20171205 VALUES IN (20171205) ENGINE = InnoDB,
PARTITION p20171204 VALUES IN (20171204) ENGINE = InnoDB,
PARTITION p20171206 VALUES IN (20171206) ENGINE = InnoDB,
PARTITION p20171207 VALUES IN (20171207) ENGINE = InnoDB) */
解读:以上为 uuid小于5时放到p0分区下,uuid大于5且小于10放到p1分区下,uuid大于10且小于15放到p2分区下,uuid大于15 一直到最大值的存在p3分区下
2. LIST分区:
CREATE TABLE tbl_test (
uuid INT NOT NULL,
title VARCHAR(20)
)
)
PARTITION BY List (uuid) (
PARTITION p0 VALUES in (1,2,3,5),
PARTITION p1 VALUES in (7,9,10),
PARTITION p2 VALUES in (11,15)
)
);
解读:以上为uuid 等于1/2/3/5时放到p0分区,7/9/10放到p1分区,11/15放到p2分区。当时用insert into时 如果uuid的值不存在p0/p1/p2分区时,则会插入失败而报错。
3. HASH分区:
HASH分区主要用来确保数据在预先确定数目的分区中平均分布。在RANGE分区和LIST分区中必须明确指定一个指定的列值或列值集合以指定应该保存在哪个分区中。而在HASH分区中,MySQL会自动完成这些工作,要做的只是基于将要被哈希的列值指定一个表达式,以及指定被分区的表将要被分割成的分区数量,如:
CREATE TABLE tbl_test (
uuid INT NOT NULL,
title VARCHAR(20)
))
PARTITION BY HASH (uuid) (
PARTITIONS 3
));
解读:MySQL自动创建3个分区,在执行insert into时,根据插入的uuid通过算法来自动分配区间。
注意:
(1) 由于每次插入、更新、删除一行,这个表达式都要计算一次,这意味着非常复杂的表达式可能会引起性能问题,尤其是在执行同时影响大量行的运算(例如批量插入)的时候。
(2) 最有效率的哈希函数是只对单个表列进行计算,并且它的值随列值进行一致的增大或减小,因为这考虑了在分区范围上的“修剪”。也就是说,表达式值和它所基于的列的值变化越接近,就越能有效地使用该表达式来进行HASH分区。
3.1:线性HASH分区
线性HASH分区在“PARTITION BY”子句中添加“LINEAR”关键字。
线性HASH分区的有点在于增加、删除、合并和拆分分区将变得更加快捷,有利于处理含有及其大量数据的表。它的缺点在于各个分区间数据的分布不大可能均衡。
4. KEY分区
类似于HASH分区,HASH分区允许用户自定义的表达式,而KEY分区则不允许使用用户自定义的表达式;HASH分区只支持整数分区,KEY分区支持除了blob和text类型之外的其他数据类型分区。
与HASH分区不同,创建KEY分区表的时候,可以不指定分区键,默认会选择使用主键或唯一键作为分区键,没有主键或唯一键,就必须指定分区键。
CREATE TABLE tbl_test (
uuid INT NOT NULL,
title VARCHAR(20)
))
PARTITION BY LINEAR Key (uuid)
PARTITIONS 3;
解读:根据分区键来进行分区
5. 子分区
子分区是分区表中,每个分区的再次分割,适合保存非常大量的数据。
CREATE TABLE tbl_test (
registerTime Date
))
PARTITION BY GANGE(YEAR(registerTime))
SUBPARTITION BY HASH (TO_DAYS(registerTime))
SUBPARTITIONS 2
(
PARTITION p0 VALUES LESS THAN (2017),
PARTITION p1 VALUES LESS THAN (2020),
PARTITION p2 VALUES LESS THAN MAXVALUE
);
解读:主分区使用RANGE按照年来进行分区,有3个RANGE分区。这3个分区中又被进一步分成了2个子分区,实际上,整个表被分成了3 * 2 = 6个分区。每个子分区按照天进行HASH分区。小于2017的放在一起,2017-2020的放在一起,大于2020的放在一起。
注意:
(1) 在MySQL5.1中,对于已经通过RANGE或LIST分区了的表在进行子分区是可能的。子分区既可以使用HASH分区,也可以使用KEY分区。这也被称为复合分区。
(2) 每个分区必须有相同数量的子分区。
(3) 如果在一个分区表上的任何分区上使用SUBPARTITION来明确定义任何子分区,那么就必须定义所有的子分区。
(4) 每个SUBPARTITION子句必须包含(至少)子分区的一个名字。
(5) 在每个子分区内,子分区的名字必须是惟一的,目前在整个表中,也要保持唯一。例如:
PARTITION BY RANGE(YEAR(registerTime))
SUBPARTITION BY HASH(TO_DAYS(registerTime))
(
PARTITION p0 VALUES LESS THAN (2017) (
SUBPARTITION s0,
SUBPARTITION s1
),
PARTITION p1 VALUES LESS THAN (2020) (
SUBPARTITION s2,
SUBPARTITION s3
),
PARTITION p2 VALUES LESS THAN MAXVALUE (
SUBPARTITION s4,
SUBPARTITION s5
)
)
子分区可以用于特别大的表,可以在多个磁盘间分配数据和索引。例如:
SUBPARTITION s0
DATA DIRECTORY = \'/disk0/data\'
INDEX DIRECTORY = \'/disk0/idx\'
,
,
SUBPARTITION s1
DATA DIRECTORY = \'/disk1/data\'
INDEX DIRECTORY = \'/disk1/idx\'
九、MySQL分区处理NULL值的方式
MySQL中的分区禁止空值NULL上没有进行处理,无论它是一个列值还是一个用户定义表达式的值,一般而言,在这种情况下MySQL把NULL视为0。如果你希望回避这种做法,你应该在设计表时声明列“NOT NULL”。
十、分区管理概述
可以对分区进行添加、删除、重新定义、合并或拆分等管理操作。
① RANGE和LIST分区的管理
1. 删除分区语句如:alter table tbl_test drop partition p0;
注意:
(1) 当删除了一个分区,也同时删除了该分区中所有的数据。
(2) 可以通过show create table tbl_test;来查看新的创建表的语句。
(3) 如果是LIST分区的话,删除的数据不能新增进来,因为这些行的列值包含在已经删除了的分区的值列表中。
2. 添加分区语句如:alter table tbl_test add partition(partition p3 values less than(50));
注意:
(1) 对于RANGE分区的表,只可以添加新的分区到分区列表的最高端。
(2) 对于LIST分区的表,不能添加已经包含在现有分区值列表中的任意值。
3. 如果希望能不丢失数据的条件下重新定义分区,可以使用如下语句:
ALTER TABLE tbl_name REORGANIZE PARTITION partition_list INTO(partition_definitions)
(1) 拆分分区如:
ALTER TABLE tbl_name REORGANIZE PARTITION partition_list INTO(partition s0 values less than(5),partition s1 values less than(10));
或者如:
ALTER TABLE tbl_name REORGANIZE PARTITION p0 INTO(partition s0 values in(1,2,3), partition s1 values in(4,5));
(2) 合并分区如:ALTER TABLE tbl_name REORGANIZE PARTITION s0,s1 INTO(partition p0 values in(1,2,3,4,5));
4. 删除所有分区,但保留数据,形式:ALTER TABLE tbl_name remove partitioning;
② HASH和KEY分区的管理
1. 减少分区数量语句如:ALTER TABLE tbl_name COALESCE PARTITION 2;
2. 添加分区数量语句如:ALTER TABLE tbl_name add PARTITION partitions 2;
③ 其他分区管理语句
1. 重建分区 :类似于先删除保存在分区中的所有记录,然后重新插入它们,可用于整理分区碎片。如:ALTER table tbl_name REBUILD PARTITION p2,p3;
2. 优化分区 :如果从分区中删除了大量的行,或者对一个带有可变长度的行(也就是说,有VARCHAR,BLOB或TEXT类型的列)做了许多修改,可以使用 ALTER TABLE tbl_name OPTIMIZE PARTITION来收回没有使用的空间,并整理分区数据文件的碎片。如:ALTER TABLE tbl_name OPTIMIZE PARTITION p2,p3;
3. 分析分区 :读取并保存分区的键分布,如:ALTER TABLE tbl_name ANALYZE PARTITION p2,p3;
4. 检查分区 :检查分区中的数据或索引是否已经被破坏,如:ALTER TABLE tbl_name CHECK PARTITION p2,p3;
5. 修补分区 :修补被破坏的分区,如:ALTER TABLE tbl_name REPAIR PARTITION p2,p3;
十、查看分区信息
1. 查看分区信息:select * from information_schema.partitions where table_schema=\'arch1\' and table_name = \'tbl_test\' G;
2. 查看分区上的数据:select * from tbl_test partition(p0);
3. 查看MySQL会操作的分区:explain partitions select * from tbl_test where uuid = 2;
十一、 局限性
1. 最大分区数目不能超过1024,一般建议对单表的分区数不要超过50个。
2. 如果含有唯一索引或者主键,则分区列必须包含在所有的唯一索引或者主键在内。
3. 不支持外键。
4. 不支持全文索引,对分区表的分区键创建索引,那么这个索引也将被分区。
5. 按日期进行分区很合适,因为很多日期函数可以用。但是对字符串来说合适的分区函数不太多。
6. 只有RANGE和LIST分区能进行子分区,HASH和KEY分区不能进行子分区。
7. 临时表不能被分区。
8. 分区表对于单条记录的查询没有优势。
9. 要注意选择分区的成本,没插入一行数据都需要按照表达式筛选插入的分区。
10. 分区字段尽量不要可以为null
以上是关于MySQL表分区的主要内容,如果未能解决你的问题,请参考以下文章