分区是指根据一定的规则,数据库把一个表分解成多个更小的,更容易管理的部分。就访问数据库的应用而言,逻辑上只有一个表或一个索引,但是实际上这个表可能由数 10 个物理分区对象组成,每个分区都是一个独立的对象,可以独自处理,可以作为表的一部分进行处理。分区对应用来说是完全透明的,不影响应用的业务逻辑
MySQL 分区的优点主要包括以下 4 个方面
- 和单个磁盘或者文件系统分区相比,可以存储更多数据
- 优化查询。在
where
子句中包含分区条件时,可以只扫描必要的一个或多个分区来提高查询效率;同时在涉及sum()
和count()
这类聚合函数的查询时,可以容易的在每个分区上并行处理,最终只需要汇总所有分区得到的结果 - 对于已经过期或者不需要保存的数据,可以通过删除与这些数据有关的分区来快速删除数据
- 跨多个磁盘来分散数据查询,以获得更大的查询吞吐量
使用 show variables like '%partition%';
命令来确定当前的 MySQL 是否支持分区
分区类型
range 分区:基于一个给定连续区间范围,把数据分配到不同的分区
list 分区:类似 range 分区,区别在 list 分区是基于枚举出的值列表分区,range 是基于给定的连续区间范围分区
hash 分区:基于给定的分区个数,把数据分配到不同的分区
key 分区:类似于 hash 分区
range 分区,list 分区,hash 分区都要求分区键必须是 int 类型,或者通过表达式返回 int 类型。key 分区可以使用其他类型的列(blob 或 text 列类型除外)作为分区键
无论是哪种 MySQL 分区类型,要么分区表上没有主键/唯一键,要么分区表的主键/唯一键都必须包含分区键,也就是说不能使用主键/唯一键字段之外的其他字段分区
分区的名字不区分大小写
range 分区
按照 range 分区的表是利用取值范围将数据分成分区,区间要连续并且不能互相重叠,使用 values less than
操作符进行分区定义,也使用 values less than maxvalue
子句提供给所有大于明确指定的最高值的值
create table tablename(value1 int)
partition by range(value1)(
partition p0 values less than(10),
partition p1 values less than(20),
partition p2 values less than maxvalue
)
MySQL 支持在 values less than
子句中使用表达式
list 分区
list 分区是建立离散的值列表告诉数据库特定的值属于哪个分区,list 分区在很多方面类似于 range 分区,区别在 list 分区是从属于一个枚举列表的值的集合,range 分区是从属于一个连续区间值的集合
list 分区是通过使用 partition by list(expr)
子句来实现,expr 是某列值或一个基于某列值返回一个整数值的表达式,然后通过 values in(value_list)
的方式来定义分区,其中 value_lsit
是一个逗号分隔的整数列表。与 range 分区不同,list 分区不必声明任何特定的顺序
create table tablename(v1 int)
partition by list(v1)(
partition p0 values in(1,3,5),
partition p1 values in(2,4,6))
如果试图插入的列值(或者分区表达式的返回值)不包含分区值列表中时,那么 insert
操作会失败并报错。要重点注意的是,list 分区不存在类似 values less than maxvalue
这样包含其他值在内的定义方式。将要匹配的任何值都必须在值列表中找得到
columns 分区
columns 分区解决了 range 分区和 list 分区只支持整数分区,从而导致需要额外的函数计算得到整数或者通过额外的转换表来转换为整数再分区的问题
columns 分区分为 range columns 分区和 list columns 分区,它们支持整数,日期时间,字符串三个数据类型
columns 分区的亮点除了支持数据类型增加之外,另外就是它还支持多列分区
create table tname(v1 int,v2 int)
partition by range columns(v1,v2)(
partition p0 values less than(0,10),
partition p1 values less than(10,10),
partition p2 values less than(10,20)
)
需要注意的是,range columns 分区键的比较是基于元组的比较,也就是基于字段组的比较。它先用插入的数据的第一个字段值和分区的第一个值进行比较,如果插入的第一个值小于分区的第一个值那么就不需要比较第二个值就属于该分区;如果第一个值等于分区的第一个值,开始比较第二个值同样如果第二个值小于分区的第二个值那么就属于该分区,以此类推
hash 分区
hash 分区主要用来分散热点读,确保数据在预先确定个数的分区中尽可能平均分布。对一个表执行 hash 分区时,MySQL 会对分区键应用一个散列函数,以此确定数据应当放在 N 个分区中的哪个分区中
MySQL 支持两种 hash 分区,常规 hash 分区和线性 hash 分区(linear hash 分区)。常规 hash 使用的是取模算法,线性 hash 使用的是一个线性的 2 的幂的运算法则
使用 partition by hash(expr) partitions num
子句对分区类型,分区键和分区个数进行定义,其中 expr 是某列值或一个基于某列值返回一个整数值的表达式,num 是一个非负的整数,表示分割成分区的数量,默认 num 为 1
create table tn(v1 int)
partition by hash(v1)
partitions 4;
插入 234 ,可以通过取模算法 mod(234,4) 得知数字被插到第 2 个分区表
表达式 expr 可以是 MySQL 中有效的任何函数或者其他表达式,只要它们返回一个既非常数也非随机数的整数。每当插入/更新/删除一行数据时,这个表达式都需要计算一次,这意味着非常复杂的表达式可能会引起性能问题,MySQL 也不推荐使用涉及多列的哈希表达式
常规 hash 分区在增加分区或者合并分区时会出现问题。假设原来是 5 个常规 hash 分区,现在需要新增一个常规 hash 分区,原来的取模算法是 mod(expr,5)
,根据余数 0~4 分布在 5 个分区中,现在新增一个分区后,取模算法变成 mod(expr,6)
,根据余数 0~5 分布在 6 个分区中,原来的 5 个分区中的数据大部分都需要通过重新计算重新分区。常规 hash 在分区管理上带来的代价太大了,不适合需要灵活变动分区的需求。为了降低分区管理上的代价,MySQL 提供了线性 hash 分区,分区函数是一个线性的 2 的幂的运算法则
线性 hash 分区和常规 hash 分区在语法上唯一区别是在 partition by
子句中添加 linear
关键字
create table tn(v1 int)
partition by linear hash(v1)
partitions 4;
同样的,使用线性 hash 时,指定记录保存在哪个分区是可以计算出来的,假设将要保存记录的分区编号为 N,num 是一个非负的整数,表示分割成分区的数量,那么 N 可以通过以下算法得到
- 首先,找到下一个大于等于 num 的 2 的幂,这个值设为 V,V 可以通过下面的公式得到:
V = Power(2,Ceiling(Log(2,num)))
- 其次,设置
N=F(column_list)&(V-1)
- 当
N>=num
。设置N=N&(ceiling(V/2)-1)
有意思的是,当线性 hash 的分区个数是 2 的 N 次幂时,线性 hash 的分区结果和常规 hash 的分区结果是一致的
线性 hash 分区的优点是,在分区维护(增加,删除,合并,拆分分区)时,MySQL 能够处理的更加迅速;缺点是,对比常规 hash分区的时候,线性 hash 各个分区之间数据的分布不太均衡
key 分区
按照 key 进行分区非常类似于按照 hash 进行分区,只不过 hash 分区允许使用用户自定义的表达式,而 key 分区不允许使用用户自定义的表达式,需要使用 MySQL 服务器提供的 hash 函数;同时 hash 分区只支持整数分区,而 key 分区支持除 blob 和 text 类型外其他类型的列作为分区键
用 partition by key(expr)
子句来创建一个 key 分区表,expr 是零个或者多个字段名的列表
与 hash 分区不同,创建 key 分区表的时候,可以不指定分区键,默认会首先选择使用主键作为分区键;在没有主键的情况,会选择非空唯一键作为分区键,作为分区键的唯一键必须是非空的,如果不是非空的,会报错;在没有主键,也没有唯一键的情况下,就不能不指定分区键了
和 hash 分区类似,在 key 分区中使用关键字 linear
具有同样的作用,也就是 linear key 分区时,分区的编号是通过 2 的幂算法得到的,而不是通过取模得到的
子分区
子分区(subpartitioning)是分区表中对每个分区再次分割,又被成为复合分区(composite partitionning)。可以对 range 或者 list 分区进行再分区,子分区可以使用 hash 和 key
create table tn(v date)
partition by range(year(v))
subpartition by hash(to_days(v))
subpartitions 2
(partition p0 values less than(2000),
partitioni p1 values less than(2010))
分区处理 NULL 值的方式
MySQL 不禁止在分区键上使用 null,分区键可能是一个字段或者一个用户定义的表达式。一般情况下,MySQL 的分区把 null 当作零值,或者一个最小值进行处理
range 分区中,null 值会被当作最小值来处理;list 分区中,null 值必须出现在枚举列表中,否则不被接受;hash/key 分区中,null 值会被当作零值来处理
由于针对不同的分区类型,null 值时而被当作零值处理,时而被当作最小值处理,为了避免在处理 null 值出现误判,更推荐通过设置字段非空和默认值来绕开 MySQL 默认对 null 值的处理
分区管理
range 和 list 分区管理
从分区的表中删除一个分区,可以使用 alter table drop partition
语句
alter table tn drop partition p0;
增加一个分区,使用 alter table add partition
语句。对于 range 分区,只能 add partition
添加新的分区到分区列表的最大一端
alter table tn add partition (partition p3 values less than(40))
alter table tn add partition (partition p3 values in(1,2))
增加 list 分区时,不能添加一个包含现有分区值列表中的任何值的分区,也就是说对一个固定的分区键值,必须指定并且只能指定一个唯一的分区,否则会出现错误
可以在不丢失数据的情况下,通过 alter table reorganize partition into
语句重新定义 range 或 list 分区
alter table xixi reorganize partition p2 into (partition p2 values less than(2020),partition p3 values less than maxvalue);
重新定义分区可以用来拆分一个 range 或 list 分区为多个 range 或 list 分区,也可以用来合并多个相邻 range 或 list 分区为一个 range 或 list 分区或者多个range 或 list 分区
alter table xixi reorganize partition p1,p2 into(partition p1 values less than maxvalue);
重新定义 range 或 list 分区时,只能重新定义相邻的分区,不能跳过某个 range 或 list 分区进行重新定义,同时重新定义的分区区间必须和原分区区间覆盖相同的区间;也不能使用重新定义分区来改变分区表分区的类型
hash 和 key 分区管理
通过 alter table coalesce partition
来拆分 hash 分区或者 key 分区
alter table xixi coalesce partition 2;
coalesce
不能用来增加分区的数量,通过 alter table add partition
语句来实现
alter table xixi add partition partitions 4;
通过
alter table xixi add partition partitions n
语句新增 hash 分区或者 key 分区时,是对原表新增 n 个分区,而不是增加到 n 个分区