分区表是一个独立的逻辑表,但是底层是由多个物理子表组成,MySQL实现分区表的方式,是对底层表的封装,意味着索引也是按照分区的子表定义的,而没有全局索引。所以分区表可以看成合并表的升级,是做了性能优化的智能化的分表,不能单独操作子表。
MySQL在创建表时使用PARTITION BY子句定义每个分区存放的数据。在执行查询时,优化器会根据分区定义过滤那些没有用到的分区,这样查询就无须扫描所有分区,而只需查找包含需要数据的分区就可以了。
分区表的创建语法如下:
CREATE [TEMPORARY] TABLE [IF NOT EXISTS] tbl_name
(create_definition,...)
[table_options]
[partition_options]
partition_options:
PARTITION BY
{ [LINEAR] HASH(expr)
| [LINEAR] KEY [ALGORITHM={1|2}] (column_list)
| RANGE{(expr) | COLUMNS(column_list)}
| LIST{(expr) | COLUMNS(column_list)} }
[PARTITIONS num]
[SUBPARTITION BY
{ [LINEAR] HASH(expr)
| [LINEAR] KEY [ALGORITHM={1|2}] (column_list) }
[SUBPARTITIONS num]
]
[(partition_definition [, partition_definition] ...)]
partition_definition:
PARTITION partition_name
[VALUES
{LESS THAN {(expr | value_list) | MAXVALUE}
|
IN (value_list)}]
[[STORAGE] ENGINE [=] engine_name]
[COMMENT [=] 'string' ]
[DATA DIRECTORY [=] 'data_dir']
[INDEX DIRECTORY [=] 'index_dir']
[MAX_ROWS [=] max_number_of_rows]
[MIN_ROWS [=] min_number_of_rows]
[TABLESPACE [=] tablespace_name]
[(subpartition_definition [, subpartition_definition] ...)]
subpartition_definition:
SUBPARTITION logical_name
[[STORAGE] ENGINE [=] engine_name]
[COMMENT [=] 'string' ]
[DATA DIRECTORY [=] 'data_dir']
[INDEX DIRECTORY [=] 'index_dir']
[MAX_ROWS [=] max_number_of_rows]
[MIN_ROWS [=] min_number_of_rows]
[TABLESPACE [=] tablespace_name]
详情请参考官方文档:从中可以看出,可以指定单个分区的存储引擎和存储路径。
一、分区表的优点
MERGE
, CSV
和 FEDERATED。
二、分区表的限制
三、分区表的使用场景
分区表是为了解决一个数据表中数据量过大,而影响操作性能的一种智能解决方案。以下场景适合使用分区表。
四、分区表的类型
分区表包含的类型有:Range分区、List分区,Columns分区,Hash分区、Key分区和子分区。对于 [LINEAR
] KEY
, RANGE COLUMNS
, 和 LIST COLUMNS,分区表达式中可以包含一个或多个列,对于
[LINEAR
] KEY分区,分区表达式必须是MySql提供的函数。
1、Range分区
Range意为范围、排列。Range分区顾名思义,就是对连续排列的的整数数据或通过函数转换为整数的数据用操作符VALUES LESS THAN进行分区。举例如下:
CREATE TABLE employees (
id INT NOT NULL,
name VARCHAR(64),
job_code INT NOT NULL
)
PARTITION BY RANGE (job_code) (
PARTITION p1 VALUES LESS THAN (100),
PARTITION p2 VALUES LESS THAN (1000),
PARTITION p3 VALUES LESS THAN (10000)
);
为了防止插入的数据的分区表达式的值超出分区定义的范围,MySql提供MAXVALUE表示最大值,-MAXVALUE表示最小值。在最后一个分区的后天添加PARTITION pn VALUES LESS THAN MAXVALUE。
对于分区键可能为空的数据,会插入第一个分区里面。所以若要需要额外存储分区键为NULL的数据,需要在第一个分区前面添加一个有意义值外的分区,例如对于表employees可以在p0前面添加分区p0:PARTITION p0 VALUES LESS THAN (0)。
Range分区的使用场景如下:
a、随着时间的流逝,数据会变得过时,可以批量删除的数据。特别是当你可以一次删除一个分区的时候,可以这样删:ALTER TABLE employees DROP PARTITION p1,比在整个表中删除(delete from employees where job_code < 100)要高效的多。
b、当你根据时间列进行组织数据时,或者按照递增列进行组织数据时。
c、当你需要频繁地需要按一递增列的区间进行查询或统计时。
Range分区的一个变体是RANGE COLUMNS分区。此分区可以根据多个列的值进行分区。详情请参考官方文档。
基于时间间隔的分区有两种选择:
a、若分区字段类型为DATE,TIME或DATETIME,使用能返回整数的时间函数YEAR,MONTH等,如:
CREATE TABLE members (
name VARCHAR(64) NOT NULL,
joined DATE NOT NULL
)
PARTITION BY RANGE( YEAR(joined) ) (
PARTITION p0 VALUES LESS THAN (1980),
PARTITION p1 VALUES LESS THAN (1990),
PARTITION p2 VALUES LESS THAN (2000),
PARTITION p3 VALUES LESS THAN (2010),
PARTITION p4 VALUES LESS THAN MAXVALUE
);
若分区键的类型为TIMESTAMP,可以使用函数UNIX_TIMESTAMP,例如:
CREATE TABLE members (
name VARCHAR(64) NOT NULL,
joined TIMESTAMP NOT NULL
)
PARTITION BY RANGE( UNIX_TIMESTAMP(joined) ) (
PARTITION p0 VALUES LESS THAN (UNIX_TIMESTAMP('1980-01-01 00:00:00')),
PARTITION p1 VALUES LESS THAN (UNIX_TIMESTAMP('1990-01-01 00:00:00')),
PARTITION p2 VALUES LESS THAN (UNIX_TIMESTAMP('2000-01-01 00:00:00')),
PARTITION p3 VALUES LESS THAN (UNIX_TIMESTAMP('2010-01-01 00:00:00')),
PARTITION p4 VALUES LESS THAN MAXVALUE
);
b、若分区键类型为DATE或DATETIME,还可以使用RANGE COLUMNS分区。如下:
CREATE TABLE members (
name VARCHAR(64) NOT NULL,
joined DATETIME NOT NULL
)
PARTITION BY RANGE COLUMNS(joined) (
PARTITION p0 VALUES LESS THAN ('1980-01-01'),
PARTITION p1 VALUES LESS THAN ('1990-01-01'),
PARTITION p2 VALUES LESS THAN ('2000-01-01'),
PARTITION p3 VALUES LESS THAN ('2010-01-01'),
PARTITION p4 VALUES LESS THAN MAXVALUE
);
2、List分区
List分区是按离散值进行分区,这些离散值可能没有顺序,也不连续,并且分区键的每一个值都必须包含在其中一个分区中,否则会报错。定义分区语法如下:
CREATE TABLE employees (
id INT NOT NULL,
name VARCHAR(64),
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)
);
分区键必须是整数值或者返回整数值的函数。
为List分区添加或删除与某个分区相关的数据会很高效。可以通过ALTER TABLE employees TRUNCATE PARTITION pWest或者ALTER TABLE employees DROP PARTITION pWest删除pWest分区里的数据,要比通过DELETE FROM employees WHERE store_id IN (4,12,13,14,18)删除数据高效许多。
List分区罗列了所有的可用来分区的值,若插入的数据不在这些值之中,则会插入失败并报错。若使用一个insert语句批量插入数据,对于数据表的存储引擎为事务性的情况,例如InnoDB,则全部插入失败;对于数据表的存储引擎为非事务性的情况,例如MyISAM,则错误之前的数据可以插入,错误之后的数据则插入失败。若可以忽略这种情况,则可以使用INSERT IGNORE INTO来忽略插入错误的数据,不影响其他正确数据的插入。
与Range分区一样,List分区也有List Columns变体,支持按一个或多个字段分区,且不要求必须是整数类型。
3、Columns分区
前面已经提过,Columns分区包括Range Columns分区和List Columns分区。Columns分区允许一个或多个列作为分区键且不要求分区键中的列类型为整数,所以大大提高了分区键的选择性。允许作为Columns分区键的列的数据类型如下:
3.1、Range Columns分区
Range Columns分区的语法如下:
CREATE TABLE table_name
PARTITIONED BY RANGE COLUMNS(column_list) (
PARTITION partition_name VALUES LESS THAN (value_list)[,
PARTITION partition_name VALUES LESS THAN (value_list)][,
...]
)
column_list:
column_name[, column_name][, ...]
value_list:
value[, value][, ...]
Range Columns分区与Range 分区在以下方面有显著区别:
3.2、List Columns分区
List Columns分区的特点与用法与Range Columns分区一致,此处不再累述。
4、Hash分区
Hash分区主要用来确保把数据均匀分配到指定数量的分区中。使用Range分区和List分区,你能明确知道每一个数据应该存储在哪个分区中,而使用Hash分区,MySql帮你确认应该把数据存储在哪个分区中。你仅指定用来分区的列、表达式以及分区的数量。例如:
CREATE TABLE employees (
id INT NOT NULL,
name VARCHAR(64),
store_id INT
)
PARTITION BY HASH(store_id)
PARTITIONS 4;
分区键包含的列类型应为整数或返回整数的表达式。表达式不可以返回常量或者随机数。分区键插入的值不变,Hash函数返回的值也应不变。高效的Hash函数是返回的值随着列的值的增减而作线性变化。
当使用PARTITION BY HASH时,MySQL根据分区表达式的返回值的模数来决定数据存放在num个分区中的第几个分区里。若存放的分区为N那么N=MOD(expr,num)。例如分区表t1:
CREATE TABLE t1 (col1 INT, col2 CHAR(5), col3 DATE)
PARTITION BY HASH( YEAR(col3) )
PARTITIONS 4;
那么若插入的数据中col3为'2015-09-01',数据应该存储的区域的计算公式为:MOD(YEAR('2005-09-01'),4) = MOD(2005,4) = 1。
MySql5.7还支持Hash分区的另一个变体LINEAR HASH分区,其使用了更复杂的算法,后面会介绍。
4.1、LINEAR HASH分区
MySql也支持Linear Hash分区,与常规hash算法不同,Linear Hash分区使用了一种线性为2的幂的算法。而常规Hash算法采用Hash函数值的模。在语法上,Linear Hash 分区只是在原来的基础上多了Linear关键字。如下:
CREATE TABLE employees (
id INT NOT NULL,
name VARCHAR(64),
hired DATE NOT NULL DEFAULT '1970-01-01'
)
PARTITION BY LINEAR HASH( YEAR(hired) )
PARTITIONS 4;
Linear Hash算法如下:若表达式为expr,分区数为num,插入数据的最终分区为N,假设一个比num大的2的幂值为V,那么
V = POWER(2, CEILING(LOG(2, num)))
N = expr & (V - 1)
若 N > num,则:
V = V / 2
N = N & (V - 1)
Linear Hash分区的优点是处理超大数据(TB级)的数据表时,对分区表的添加、删除,合并和切分都是非常快的;缺点是相对Hash 分区来说,数据在分区中的分布不够均匀。
5、Key分区
Key分区与Hash分区相似,只是Hash分区使用用户定义的表达式,而Key分区使用MySQL提供的Hash函数。NDB集群使用MD5也是基于此目的;对于使用其他存储引擎的数据表,服务器使用其内部的基于相同算法的Hash函数,例如:PASSWORD()。
Key分区与Hash分区的语法类似:CREATE TABLE ... PARTITION BY KEY。它们的主要区别如下:
CREATE TABLE k1 (
id INT NOT NULL PRIMARY KEY,
name VARCHAR(20)
)
PARTITION BY KEY()
PARTITIONS 2;
但是,若唯一性键的列没有设置为NOT NULL,则前面的例子就是失败的。
与其他分区类别不同的是,用来作为KEY分区的列不要求是整数类型或NULL值。Key分区也有变体Linear Key分区。使用的算法与Linear Hash分区的算法相似,此处不再累述。KEY
和 LINEAR KEY
分区可以使用NDB,而其他分区的不可以,详情参考MySql官方文档。
6、子分区
子分区又叫组合分区,是对分区表中的分区的进一步分区。创建子分区的语法如下:
CREATE TABLE ts (id INT, purchased DATE)
PARTITION BY RANGE( YEAR(purchased) )
SUBPARTITION BY HASH( TO_DAYS(purchased) )
SUBPARTITIONS 2 (
PARTITION p0 VALUES LESS THAN (1990),
PARTITION p1 VALUES LESS THAN (2000),
PARTITION p2 VALUES LESS THAN MAXVALUE
);
在MySql5.7中,可以对Range分区和List分区再进一步分区,子分区可以使用Hash分区和Key分区,所以才叫组合分区。创建子分区时,也可以通过使用SUBPARTITION
语句定义每个子分区的选项。如下:
CREATE TABLE ts (id INT, purchased DATE)
PARTITION BY RANGE( YEAR(purchased) )
SUBPARTITION BY HASH( TO_DAYS(purchased) ) (
PARTITION p0 VALUES LESS THAN (1990) (
SUBPARTITION s0,
SUBPARTITION s1
),
PARTITION p1 VALUES LESS THAN (2000) (
SUBPARTITION s2,
SUBPARTITION s3
),
PARTITION p2 VALUES LESS THAN MAXVALUE (
SUBPARTITION s4,
SUBPARTITION s5
)
);
使用子分区时,在语法上的注意事项如下:
SUBPARTITION
明确定义分区表的分区的子分区,则必须定义每个分区的子分区,不可仅定义部分分区的子分区。SUBPARTITION
语句必须至少包含子分区名称,否则,你就是允许使用系统默认设定的名字。使用MyISAM存储引擎的分区表,可以分别指定每个子分区数据和索引存储的位置,包括跨多硬盘存储。例如:
CREATE TABLE ts (id INT, purchased DATE)
ENGINE = MYISAM
PARTITION BY RANGE( YEAR(purchased) )
SUBPARTITION BY HASH( TO_DAYS(purchased) ) (
PARTITION p0 VALUES LESS THAN (1990) (
SUBPARTITION s0
DATA DIRECTORY = '/disk0/data'
INDEX DIRECTORY = '/disk0/idx',
SUBPARTITION s1
DATA DIRECTORY = '/disk1/data'
INDEX DIRECTORY = '/disk1/idx'
),
PARTITION p1 VALUES LESS THAN (2000) (
SUBPARTITION s2
DATA DIRECTORY = '/disk2/data'
INDEX DIRECTORY = '/disk2/idx',
SUBPARTITION s3
DATA DIRECTORY = '/disk3/data'
INDEX DIRECTORY = '/disk3/idx'
),
PARTITION p2 VALUES LESS THAN MAXVALUE (
SUBPARTITION s4
DATA DIRECTORY = '/disk4/data'
INDEX DIRECTORY = '/disk4/idx',
SUBPARTITION s5
DATA DIRECTORY = '/disk5/data'
INDEX DIRECTORY = '/disk5/idx'
)
);
若MySql设置为NO_DIR_IN_CREATE模式,则DATA DIRECTORY
和 INDEX DIRECTORY是不允许设置的。
7、MySql分区如何处理NULL
MySql分区没有限制NULL值做分区表达式的值,而是当需要排序时把NULL当初小于任何一个NON-NULL值的值。但每一种分区对NULL的处理是不同的。若不注意对NULL的处理,会造成你意料之外的情况。
五、管理分区表
MySQL5.7提供很多方法修改分区表。包括新增、删除、重定义、合并和拆分已存在的分区表。所有这些操作都通过ALTER TABLE语句的扩展来实现。仅通过ALTER TABLE tablename partition_options 就可以修改一个表的分区方案(即:修改分区类型),其中partition_options 与创建分区表中一样。ALTER TABLE 后面只能跟一个修改语句(PARTITION BY
, ADD PARTITION
, DROP PARTITION
, REORGANIZE PARTITION
, 或 COALESCE PARTITION
),不可联合使用。要删除一个分区里的数据,需要执行语句: ALTER TABLE ... TRUNCATE PARTITION。
1、管理Range分区和List分区。
对Range分区和List分区来说,新增和删除分区是一样的形式,所以在此一起介绍。删除分区的语法如下:
ALTER TABLE table_name
DROP PARTITION partition_name;
删除一个分区时,分区内的数据也一同被删除。如果要想在保留数据的前提下删除数据,就需要使用重组分区语句:ALTER TABLE ... REORGANIZE PARTITION。
查询一个分区里的数据,可以通过分区键的过滤条件来查,也可通过制定分区名称来查,例如:
SELECT * FROM table_name PARTITION (partition_name);
新增分区的语法是:
ALTER TABLE table_name ADD PARTITION partition_name
对于Range分区,只能在最后添加分区。若想在中间或前面添加分区,就需要使用重组分区的语法。把其中一个分区分成多个分区,并且分区必须是连续的,不可有重叠或跨越。例如:
ALTER TABLE table_name
REORGANIZE PARTITION p1 INTO (
PARTITION n0 VALUES LESS THAN (value1),
PARTITION n1 VALUES LESS THAN (value2)
);
对于List分区,新增的分区不能包括当前已存在的分区键值。若想重组已有分区或新增列举值再重新分区,也需要使用重组分区语法,例如:
/*原分区表结构*/
CREATE TABLE tt (
id INT,
data INT
)
PARTITION BY LIST(data) (
PARTITION p0 VALUES IN (5, 10, 15),
PARTITION p1 VALUES IN (6, 12, 18)
);
/*新增分区*/
ALTER TABLE tt ADD PARTITION (PARTITION np VALUES IN (4, 8));
/*重组分区*/
ALTER TABLE tt REORGANIZE PARTITION p1,np INTO (
PARTITION p1 VALUES IN (6, 18),
PARTITION np VALUES in (4, 8, 12)
);
重组分区可以把一个分区拆分成几个分区,也可以把多个分区合并成一个分区,甚至把n个分区重组为m个分区。语法如下:
ALTER TABLE table_name
REORGANIZE PARTITION partition_list
INTO (partition_definitions);
2、Hash分区和Key分区的管理
Hash分区与Key分区的管理方式相同,而与Range分区及List分区的管理差别很大。删除分区的方式与Rang分区及List分区的方式相同,而合并分区的语法则为: ALTER TABLE table_name COALESCE PARTITION n。例如对于Hash分区表:
CREATE TABLE clients (
id INT,
name VARCHAR(64),
signed DATE
)
PARTITION BY HASH( MONTH(signed) )
PARTITIONS 12;
若要从12个分区缩减为8个分区,执行操作如下:
ALTER TABLE clients COALESCE PARTITION 4;
其中4是减少的分区数,所以减少的分区数不可大于分区总数。若要增加分区数,例如从12个分区增加到18个分区,执行如下操作:
ALTER TABLE clients ADD PARTITION PARTITIONS 6;
3、交换表之间分区或子分区(里的数据)。
在MySql5.7中,可以通过语法 ALTER TABLE pt EXCHANGE PARTITION pWITH TABLE nt交换一个表分区或分区与另一个表中的数据。其中pt是分区表,p为pt中的一个分区或子分区,nt为未分区的普通表。对于非分区表的nt,还有以下要求:
执行ALTER TABLE pt EXCHANGE PARTITION pWITH TABLE nt语句的影响如下:
默认情况下,交换数据会逐行验证数据的合法性,若添加选项 WITHOUT VALIDATION 则忽略验证,以提高交换效率,语法如下:ALTER TABLE pt EXCHANGE PARTITION p WITH TABLE nt WITH VALIDATION,只是数据库管理员要对数据合法性负责任。若真出现不匹配的数据,DBA可以通过以下语句解决: REPAIR TABLE or ALTER TABLE ... REPAIR PARTITION。
交换子分区数据也是同样方式,通过此语句获取子分区名称:
SELECT PARTITION_NAME, SUBPARTITION_NAME, TABLE_ROWS
FROM INFORMATION_SCHEMA.PARTITIONS
WHERE TABLE_NAME = 'pt';
4、分区维护
在MySql5.7.2以后才支持分区维护功能。表的维护语句也适用于分区表:CHECK TABLE, OPTIMIZE TABLE, ANALYZE TABLE, and REPAIR TABLE。分区的维护与此类似,同样使用ALTER TABLE的扩展来实现。
ALTER TABLE t1 REBUILD PARTITION p0, p1;
优化分区:如果删除了分区里的大类数据或更新了分区里的大量数据,则通过优化分区来回收未被使用的空间及碎片整理,等价于执行了CHECK PARTITION, ANALYZE PARTITION, 和 REPAIR PARTITION,优化分区语法为:
ALTER TABLE t1 OPTIMIZE PARTITION p0, p1;
有些存储引擎(包括InnoDB)在同时优化所有分区时,会报异常,可通过 REBUILD PARTITION和ANALYZE PARTITION来替代。
分析分区:读取和存储分区的键分布。语法如下:
ALTER TABLE t1 ANALYZE PARTITION p3;
修复分区:修复崩溃的分区。若分区中有重复的主键或唯一性键,则修复失败,这时可以使用 ALTER IGNORE TABLE后加修复分区选项。语法如下:
ALTER TABLE t1 REPAIR PARTITION p0,p1;
检查分区:检查分区中的错误,包括数据和索引崩溃。若分区中有重复的主键或唯一性键,则检查失败,这时可以使用 ALTER IGNORE TABLE后加检查分区选项。语法为:
ALTER TABLE trb3 CHECK PARTITION p1;
另外,这些维护语句都支持在PARTITION透明添加ALL来维护所有分区。检查工具 mysqlcheck 和 myisamchk不适用分区表。
六、分区剪切
分区剪切是一种分区优化。其背后的核心理念是很简单的,可以用一句话来描述:不扫描为匹配数据的分区。
只要where条件减少为以下两种情况之一,优化器就执行剪切处理。
1、partition_column = constant。优化器评估哪个分区包含这个值,就只扫描哪个分区。等号还可以延伸为<、>、<=、>=、<>,有些查询中BETWEEN也可以使用剪切。注意constant是常量值。
2、partition_column IN (constant1, constant2, ..., constantN):优化器苹果列表中每个值所在的分区,然后通过仅扫描这些分区来实现剪切效果。
七、显式分区选择
MySQL5.7支持显式分区选择。显式分区选择与分区剪切相似,都只检查扫描匹配的分区,也有不同之处:
显式分区选择适用的语句包括:SELECT,DELETE,INSERT,UPDATE,REPLACE,LOAD DATA,LOAD XML。显式分区选择的语法为:
PARTITION (partition_names)
partition_names:
partition_name, ...