分区
对一个表做分区有很多种方法:
RANGE
:这种类型的区分根据落在给定范围内的列值,将行分配给分区LIST
:类似于按RANGE分区,不同的是其分区是基于一组离散值匹配的列来选择的HASH
:在这种类型的分区操作中,一个分区是根据用户定义的表达式返回值来选择的,该表达式对插入到表的行中的列值进行操作。HASH函数可以包含任何在MySQL中具有非负整数值的有效表达式KEY
:这种类型的分区类似于HASH分区,只是它仅提供一个或多个列,而且MySQL服务器提供自己的哈希函数。这些列可以包含除整数值以外的其他值,因为MySQL提供的哈希函数保证结果都未整数。
上述每一个分区类型都有一个扩展。RANGE的扩展为RANGE COLUMNS,LIST的扩展为LIST COLUMN,HASH的扩展为LINEAR HASH,KEY的扩展为LINEAR KEY。
对于[LINEAR] KEY、RANGE COLUMNS和LIST COLUMNS分区,分区表达式包含一个或多个列的列表。
在RANGE、LIST和[LINEAR] HASH分区中,分区的列的值被传递给分区函数,该函数返回一个整数值,表示该特定记录应该被存储在第几个分区。这个函数的返回值必须为既非常数也非随机数。
如何操作
可以在创建表时指定分区,也可以通过ALTER TABLE命令来指定分区。分区列应该是表中所有唯一键的一部分。
如果基于created_at列定义了分区,并且id是主键,则应该将created_at列作为PRIMARY KEY的一部分包含在内,即(id, created_at)。
RANGE分区
如果想要根据emp_no对employees表分区,并希望在一个分区中保留100000名员工,可以这样创建分区:
-- 所有emp_no小于100000的员工划入分区p0,所有小于200000大于100000的员工划入分区p1,以此类推
create table `employees` (
emp_no int(11) not null,
birth_date date not null,
first_name varchar(14) not null,
last_name varchar(16) not null,
gender enum('M', 'F') not null,
hire_date date not null,
address varchar(100) default null,
primary key (emp_no),
key `name` (first_name, `last_name`)
) engine = innodb default charset = utf8mb4
partition by range (emp_no)
(partition p0 values less than (100000) engine = innodb,
partition p1 values less than (200000) engine = innodb,
partition p2 values less than (300000) engine = innodb,
partition p3 values less than (400000) engine = innodb,
partition p4 values less than (500000) engine = innodb);
-- 注意:如果员工编号大于500000,因为没有为这些编号定义分区,所以这时插入操作将会失败,并报错。为了避免这种情况,必须定期检查并添加分区或创建一个MAXVALUE分区,以捕获所有类似这种情况的异常:
create table `employees` (
emp_no int(11) not null,
birth_date date not null,
first_name varchar(14) not null,
last_name varchar(16) not null,
gender enum('M', 'F') not null,
hire_date date not null,
address varchar(100) default null,
primary key (emp_no),
key `name` (first_name, `last_name`)
) engine = innodb default charset = utf8mb4
partition by range (emp_no)
(partition p0 values less than (100000) engine = innodb,
partition p1 values less than (200000) engine = innodb,
partition p2 values less than (300000) engine = innodb,
partition p3 values less than (400000) engine = innodb,
partition p4 values less than (500000) engine = innodb,
partition pmax values less than maxvalue engine = innodb);
如果想基于hire_date分区,可以使用YEAR(hire_date)
函数作为分区表达式:
create table `employees` (
emp_no int(11) not null,
birth_date date not null,
first_name varchar(14) not null,
last_name varchar(16) not null,
gender enum('M', 'F') not null,
hire_date date not null,
address varchar(100) default null,
primary key (emp_no, hire_date),
key `name` (first_name, `last_name`)
) engine = innodb default charset = utf8mb4
partition by range (year(hire_date))
(partition p1980 values less than (1980) engine = innodb,
partition p1990 values less than (1990) engine = innodb,
partition p2000 values less than (2000) engine = innodb,
partition p2010 values less than (2010) engine = innodb,
partition p2020 values less than (2020) engine = innodb,
partition pmax values less than maxvalue engine = innodb);
MySQL中的分区被广泛应用与date、datetime或timestamp列。如果想在数据库中存储一些事件,并且所有的查询都基于一个时间范围,则可以像这样使用分区。
分区函数to_days()
返回自0000-01-01以来的天数,这是一个整数值:
create table `event_history` (
event_id int(11) not null,
envent_name varchar(10) not null,
created_at datetime not null,
last_updated timestamp default current_timestamp on update current_timestamp,
event_type varchar(10) not null,
msg tinytext not null,
primary key (event_id, created_at)
) engine = innodb default charset = utf8mb4
partition by range (to_days(created_at))
(partition p20170930 values less than (736967) engine = innodb,
partition p20171001 values less than (736968) engine = innodb,
partition p20171002 values less than (736969) engine = innodb,
partition p20171003 values less than (736970) engine = innodb,
partition p20171004 values less than (736971) engine = innodb,
partition p20171005 values less than (736972) engine = innodb,
partition p20171006 values less than (736973) engine = innodb,
partition p20171007 values less than (736974) engine = innodb,
partition p20171008 values less than (736975) engine = innodb,
partition p20171009 values less than (736976) engine = innodb,
partition p20171010 values less than (736977) engine = innodb,
partition p20171011 values less than (736978) engine = innodb,
partition p20171012 values less than (736979) engine = innodb,
partition p20171013 values less than (736980) engine = innodb,
partition p20171014 values less than (736981) engine = innodb,
partition p20171015 values less than (736982) engine = innodb,
partition pmax values less than maxvalue engine = innodb);
如果希望将现有的一个表转换为分区的表,如果分区键不是主键的一部分,则需要删除(drop)主键,并将分区键作为主键和所有唯一键的一部分添加进来。否则,将收到如下报错:
可以如下操作:
alter table employees drop primary key, add primary key(emp_no, hire_date);
alter table employees partition by range (year(hire_date))
(partition p1980 values less than (1980) engine = innodb,
partition p1990 values less than (1990) engine = innodb,
partition p2000 values less than (2000) engine = innodb,
partition p2010 values less than (2010) engine = innodb,
partition p2020 values less than (2020) engine = innodb,
partition pmax values less than maxvalue engine = innodb);
RANGE COLUMNS 分区
RANGE COLUMNS分区类似于RANGE分区,但是它允许使用基于多个列值的范围来定义分区。此外,你可以使用非整数类型的列来定义范围。RANGE COLUMNS分区与RANGE分区在一下几方面有很大的区别:
- RANGE COLUMNS不接受表达式,只接受列的名称
- RANGE COLUMNS接受一个或多个列的列表
- RANGE COLUMNS的分区列不限于整数列;字符串、DATE和DATETIME列也可以用作分区列
-- 在RANGE COLUMNS中可以直接使用hire_date列,而不是使用to_days()或year()函数
alter table employees partition by range columns (hire_date)
(partition p0 values less than ('1970-01-01'),
partition p1 values less than ('1980-01-01'),
partition p2 values less than ('1990-01-01'),
partition p3 values less than ('2000-01-01'),
partition p4 values less than ('2010-01-01'),
partition p5 values less than (maxvalue));
-- 可以根据员工的姓氏(last_name)来划分员工,这么做不能保证员工在各个分区之间均匀分布
alter table employees drop primary key, add primary key(emp_no, last_name);
alter table employees partition by range columns (last_name)
(partition p0 values less than ('b'),
partition p1 values less than ('f'),
partition p2 values less than ('l'),
partition p3 values less than ('u'),
partition p4 values less than ('z'));
使用RANGE COLUMNS,可以在分区函数中放置多个列:
create table range_column_example (
a int,
b int,
c int,
d int,
e int,
primary key(a, b, c)
)
partition by range columns (a, b, c) (
partition p0 values less than (0, 25, 50),
partition p1 values less than (10, 50, 100),
partition p2 values less than (10, 100, 200),
partition p3 values less than (maxvalue, maxvalue, maxvalue)
);
如果插入值a=10,b=20,c=100,d=100,e=100,则它被分配到p1分区。在通过RANGE COLUMNS设计分区表时,可以利用mysql客户端比较所需的元组来测试连续分区的定义,如下所示:
LIST和LIST COLUMNS分区
LIST分区与RANGE分区类似,其每个分区都是根据一组值列表中的一个列值的成员来定义和选择的,而不是在一组连续的值范围内进行。
需要通过PARTITION BY LIST(<expr>)
来定义它,其中expr是一个列值或基于列值的表达式,并返回一个整数值。
分区定义包含VALUES IN(<value_list>)
,其中value_list是一个用逗号分隔的整数列表,而不是VALUES LESS THAN (<value>)
。
如果希望使用除整数以外的数据类型,可以使用LIST COLUMNS。
与RANGE分区的情况不同,这里没有像MAXVALUE之类的catch-alI,分区表达式期望的所有值都应该包含在PARTITION表达式中。
假设有一张带有邮政编码和城市信息的客户表。如果你想在分区中按照特定的邮政编码来划分客户,可以使用工LIST分区:
create table customer (
customer_id int,
zipcode int,
city varchar(100),
primary key (customer_id, zipcode)
)
partition by list(zipcode) (
partition pnorth values in (560030, 560007, 560051, 560084),
partition peast values in (560040, 560008, 560061, 560085),
partition pwest values in (560050, 560009, 560062, 560086),
partition pcentral values in (560060, 560010, 560063, 560087)
);
-- 如果希望 直接使用列而不是整数,则可以使用LIST COLUMNS来分区:
create table customer (
customer_id int,
zipcode int,
city varchar(100),
primary key (customer_id, city)
)
partition by list columns(city) (
partition pnorth values in ('city1', 'city2', 'city3'),
partition peast values in ('city4', 'city5', 'city6'),
partition pwest values in ('city7', 'city8', 'city9'),
partition pcentral values in ('city10', 'city11', 'city12')
);
HASH和LINEAR HASH分区
HASH分区主要是为了确保数据均匀地分布在数量预先确定的一组分区中。使用RANGE或工IST分区的话,必须明确指定应该将给定的列值或列值集合存储在哪一个分区中;而
如果使用HASH分区,这个决定将由你来做,你只需要根据要进行哈希的列值指定一个列值或表达式,以及分区表要分为多少个分区即可。
如果你希望员工在分区中均匀地分布,可以指定分区的数量,并根据YEAR(hire_date)
进行HASH分区,而不是根据YEAR(hire_date)
进行RANGE分区。当使用PARTITION BY HASH时,存储引擎会根据该表达式结果的模来确定要使用哪一个分区。
例如,如果hire_date是1987-11-28,YEAR(hire_date)将是1987,MOD(1987, 8)的结果是3,所以这一样将被分到第三个分区:
create table `employees` (
emp_no int(11) not null,
birth_date date not null,
first_name varchar(14) not null,
last_name varchar(16) not null,
gender enum('M', 'F') not null,
hire_date date not null,
address varchar(100) default null,
primary key (emp_no, hire_date),
key `name` (first_name, `last_name`)
) engine = innodb default charset = utf8mb4
partition by hash (year(hire_date))
partitions 8;
效率最高的哈希函数是对单个列进行操作的函数,其值与该列值同步地增加或减少。
在LINEAR HASH分区中,可以使用相同的语法,只不过要添加LINEAR关键字。MySQL不使用MODULUS操作,而是使用2的幂算法来确定分区。
create table `employees` (
emp_no int(11) not null,
birth_date date not null,
first_name varchar(14) not null,
last_name varchar(16) not null,
gender enum('M', 'F') not null,
hire_date date not null,
address varchar(100) default null,
primary key (emp_no, hire_date),
key `name` (first_name, `last_name`)
) engine = innodb default charset = utf8mb4
partition by linear hash (year(hire_date))
partitions 8;
KEY和LINEAR KEY分区
KEY分区与HASH分区类似,不同之处在于,HASH分区使用用户定义的表达式,KEY分区的哈希函数由MySQL服务器提供。这个内部哈希函数采用的是与PASSWORD()函数
相同的算法。
KEY仅包含零个或几个列名称的列表。如果表有主键的话,则用作KEY分区的任何列都必须是主键的一部分或全部。如果没有列名可以被指定为分区键,则使用表的主键(如果有的话):
create table `employees` (
emp_no int(11) not null,
birth_date date not null,
first_name varchar(14) not null,
last_name varchar(16) not null,
gender enum('M', 'F') not null,
hire_date date not null,
address varchar(100) default null,
primary key (emp_no, hire_date),
key `name` (first_name, `last_name`)
) engine = innodb default charset = utf8mb4
partition by linear key()
partitions 8;
子分区
可以将每个分区进一步划分为一个分区表,称为子分区或复合分区
create table `employees` (
emp_no int(11) not null,
birth_date date not null,
first_name varchar(14) not null,
last_name varchar(16) not null,
gender enum('M', 'F') not null,
hire_date date not null,
address varchar(100) default null,
primary key (emp_no, hire_date),
key `name` (first_name, `last_name`)
) engine = innodb default charset = utf8mb4
partition by range (year(hire_date))
subpartition by hash (emp_no)
subpartitions 4 (
partition p0 values less than (1990),
partition p1 values less than (2000),
partition p2 values less than (2010),
partition p3 values less than (2020),
partition p4 values less than maxvalue
);
删除分区
如果希望删除分区,可以执行REMOVE PARTITIONING
语句:
alter table employees remove partitioning;