MySQL 5.7 分区键、主键和唯一键的关系

分区键、主键和唯一键
这里将讨论分区键与主键和唯一键的关系。控制这种关系的规则可以表达如下:分区表的分区表达式中使用的所有列必须是表可能具有的每个唯一 键的一部分。

换句话说,表上的每个唯一键都必须使用表的分区表达式中的每一列。(这也包括表的主键,因为根据定义,它是唯一的键。本节稍后将讨论这 种特殊情况。)例如,下列创建表的语句都是无效的:

mysql> CREATE TABLE t1 (
    -> col1 INT NOT NULL,
    -> col2 DATE NOT NULL,
    -> col3 INT NOT NULL,
    -> col4 INT NOT NULL,
    -> UNIQUE KEY (col1, col2)
    -> )
    -> PARTITION BY HASH(col3)
    -> PARTITIONS 4;
ERROR 1503 (HY000): A PRIMARY KEY must include all columns in the table's partitioning function
mysql> CREATE TABLE t2 (
    -> col1 INT NOT NULL,
    -> col2 DATE NOT NULL,
    -> col3 INT NOT NULL,
    -> col4 INT NOT NULL,
    -> UNIQUE KEY (col1),
    -> UNIQUE KEY (col3)
    -> )
    -> PARTITION BY HASH(col1 + col3)
    -> PARTITIONS 4;
ERROR 1503 (HY000): A PRIMARY KEY must include all columns in the table's partitioning function

在每种情况下,建议的表都至少有一个唯一的键,不包括分区表达式中使用的所有列。

下列语句都是有效的,表示了相应的无效表创建语句的一种工作方式:

mysql> CREATE TABLE t1 (
    -> col1 INT NOT NULL,
    -> col2 DATE NOT NULL,
    -> col3 INT NOT NULL,
    -> col4 INT NOT NULL,
    -> UNIQUE KEY (col1, col2, col3)
    -> )
    -> PARTITION BY HASH(col3)
    -> PARTITIONS 4;
Query OK, 0 rows affected (0.02 sec)

mysql> CREATE TABLE t2 (
    -> col1 INT NOT NULL,
    -> col2 DATE NOT NULL,
    -> col3 INT NOT NULL,
    -> col4 INT NOT NULL,
    -> UNIQUE KEY (col1, col3)
    -> )
    -> PARTITION BY HASH(col1 + col3)
    -> PARTITIONS 4;
Query OK, 0 rows affected (0.01 sec)

下面的例子显示了在这种情况下产生的错误:

mysql> CREATE TABLE t3 (
    -> col1 INT NOT NULL,
    -> col2 DATE NOT NULL,
    -> col3 INT NOT NULL,
    -> col4 INT NOT NULL,
    -> UNIQUE KEY (col1, col2),
    -> UNIQUE KEY (col3)
    -> )
    -> PARTITION BY HASH(col1 + col3)
    -> PARTITIONS 4;
ERROR 1503 (HY000): A PRIMARY KEY must include all columns in the table's partitioning function

CREATE TABLE语句失败了,因为col1和col3都包含在建议的分区键中,但是这些列都不是表上两个唯一键的一部分。这是对无效表定义的一种可 能的修复:

mysql> CREATE TABLE t3 (
    -> col1 INT NOT NULL,
    -> col2 DATE NOT NULL,
    -> col3 INT NOT NULL,
    -> col4 INT NOT NULL,
    -> UNIQUE KEY (col1, col2, col3),
    -> UNIQUE KEY (col3)
    -> )
    -> PARTITION BY HASH(col3)
    -> PARTITIONS 4;
Query OK, 0 rows affected (0.03 sec)

在这种情况下,建议的分区键col3是两个唯一键的一部分,并且创建表语句成功。

下面的表根本不能分区,因为没有办法在分区键中包含属于两个唯一键的任何列:

CREATE TABLE t4 (
col1 INT NOT NULL,
col2 INT NOT NULL,
col3 INT NOT NULL,
col4 INT NOT NULL,
UNIQUE KEY (col1, col3),
UNIQUE KEY (col2, col4)
);

由于每个主键根据定义都是唯一键,因此此限制还包括表的主键(如果有的话)。例如,下面两个语句是无效的:

mysql> CREATE TABLE t5 (
    -> col1 INT NOT NULL,
    -> col2 DATE NOT NULL,
    -> col3 INT NOT NULL,
    -> col4 INT NOT NULL,
    -> PRIMARY KEY(col1, col2)
    -> )
    -> PARTITION BY HASH(col3)
    -> PARTITIONS 4;
ERROR 1503 (HY000): A PRIMARY KEY must include all columns in the table's partitioning function
mysql> CREATE TABLE t6 (
    -> col1 INT NOT NULL,
    -> col2 DATE NOT NULL,
    -> col3 INT NOT NULL,
    -> col4 INT NOT NULL,
    -> PRIMARY KEY(col1, col3),
    -> UNIQUE KEY(col2)
    -> )
    -> PARTITION BY HASH( YEAR(col2) )
    -> PARTITIONS 4;
ERROR 1503 (HY000): A PRIMARY KEY must include all columns in the table's partitioning function

在这两种情况下,主键都不包括分区表达式中引用的所有列。但是,下面两个语句都是有效的:

mysql> CREATE TABLE t7 (
    -> col1 INT NOT NULL,
    -> col2 DATE NOT NULL,
    -> col3 INT NOT NULL,
    -> col4 INT NOT NULL,
    -> PRIMARY KEY(col1, col2)
    -> )
    -> PARTITION BY HASH(col1 + YEAR(col2))
    -> PARTITIONS 4;
Query OK, 0 rows affected (0.02 sec)

mysql> CREATE TABLE t8 (
    -> col1 INT NOT NULL,
    -> col2 DATE NOT NULL,
    -> col3 INT NOT NULL,
    -> col4 INT NOT NULL,
    -> PRIMARY KEY(col1, col2, col4),
    -> UNIQUE KEY(col2, col1)
    -> )
    -> PARTITION BY HASH(col1 + YEAR(col2))
    -> PARTITIONS 4;
Query OK, 0 rows affected (0.02 sec)

如果一张表没有唯一键(包括没有主键),那么这个限制就不适用了,只要列类型与分区类型兼容,就可以在分区表达式中使用任意一列或多列 。
出于同样的原因,用户不能在分区表中添加唯一键,除非这个键包含了表的分区表达式所使用的所有列。考虑如下所示创建的分区表:

mysql> CREATE TABLE t_no_pk (c1 INT, c2 INT)
    -> PARTITION BY RANGE(c1) (
    -> PARTITION p0 VALUES LESS THAN (10),
    -> PARTITION p1 VALUES LESS THAN (20),
    -> PARTITION p2 VALUES LESS THAN (30),
    -> PARTITION p3 VALUES LESS THAN (40)
    -> );
Query OK, 0 rows affected (0.02 sec)

可以使用ALTER TABLE语句为t_no_pk添加一个主键:

mysql> ALTER TABLE t_no_pk ADD PRIMARY KEY(c1);
Query OK, 0 rows affected (0.05 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> ALTER TABLE t_no_pk DROP PRIMARY KEY;
Query OK, 0 rows affected (0.03 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> ALTER TABLE t_no_pk ADD PRIMARY KEY(c1, c2);
Query OK, 0 rows affected (0.02 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> ALTER TABLE t_no_pk DROP PRIMARY KEY;
Query OK, 0 rows affected (0.02 sec)
Records: 0  Duplicates: 0  Warnings: 0

然而,下一条语句失败了,因为c1是分区键的一部分,但不是建议的主键的一部分:

mysql> ALTER TABLE t_no_pk ADD PRIMARY KEY(c2);
ERROR 1503 (HY000): A PRIMARY KEY must include all columns in the table's partitioning function

由于t_no_pk的分区表达式中只有c1,因此试图单独在c2上添加唯一键会失败。但是,您可以添加一个同时使用c1和c2的唯一键。

这些规则也适用于希望使用ALTER TABLE …PARTITION BY.的现有未分区表。。考虑一个表np_pk,如下所示:

mysql> CREATE TABLE np_pk (
    -> id INT NOT NULL AUTO_INCREMENT,
    -> name VARCHAR(50),
    -> added DATE,
    -> PRIMARY KEY (id)
    -> );
Query OK, 0 rows affected (0.01 sec)

下面的ALTER TABLE语句返回错误,因为添加的列不是表中任何唯一键的一部分:

mysql> ALTER TABLE np_pk PARTITION BY HASH( TO_DAYS(added) ) PARTITIONS 4;
ERROR 1503 (HY000): A PRIMARY KEY must include all columns in the table's partitioning function

但是,使用id列作为分区列的语句是有效的,如下所示:

mysql> ALTER TABLE np_pk PARTITION BY HASH(id) PARTITIONS 4;
Query OK, 0 rows affected (0.03 sec)
Records: 0  Duplicates: 0  Warnings: 0

在np_pk的情况下,唯一可以用作分区表达式一部分的列是id;如果用户希望在分区表达式中使用任何其他列对该表进行分区,则必须首先修改表,要么将所需的列添加到主键中,要么完全删除主键。

MySQL 5.7 分区的限制和约束条件

分区的限制和约束条件
这里将讨论MySQL分区支持的当前限制和约束条件。

禁止结构。分区表达式中不允许使用以下结构:
.存储过程、存储函数、自定义函数或插件。
.声明的变量或用户变量

算术和逻辑运算符。分区表达式中允许使用算术运算符+、-和*。但是,结果必须是整数值或NULL (线性键分区除外)
还支持DIV操作符,不允许使用/操作符。(Bug #30188, Bug #33182)
位操作符|、&、^、< <、>>和~不允许出现在分区表达式中。
HANDLER语句。以前,分区表不支持HANDLER语句。这个限制从MySQL 5.7.1开始被移除。

服务器SQL模式。使用用户定义分区的表在创建时不会保留有效的SQL模式。正如5.1.8节所讨论的,许多MySQL函数和操作符的结果可能会根据服 务器SQL模式而改变。因此,在创建分区表之后的任何时候更改SQL模式都可能导致这些表的行为发生重大变化,并且很容易导致数据损坏或丢失 。出于这些原因,强烈建议在创建分区表后不要更改服务器 SQL模式。

例如:下面的示例说明了由于服务器SQL模式的更改而导致的分区表行为的一些变化:
1.错误处理。假设您创建了一个分区表,其分区表达式为列DIV 0或列MOD 0,如下所示:

mysql> CREATE TABLE tn (c1 INT)
    -> PARTITION BY LIST(1 DIV c1) (
    -> PARTITION p0 VALUES IN (NULL),
    -> PARTITION p1 VALUES IN (1)
    -> );
Query OK, 0 rows affected (0.03 sec)

MySQL的默认行为是对除零的结果返回NULL,不会产生任何错误:

mysql> SELECT @@sql_mode;
+------------+
| @@sql_mode |
+------------+
|            |
+------------+
1 row in set (0.00 sec)

mysql> INSERT INTO tn VALUES (NULL), (0), (1);
Query OK, 3 rows affected (0.00 sec)
Records: 3  Duplicates: 0  Warnings: 0

但是,更改服务器SQL模式,将除零视为错误,并强制执行严格的错误处理,会导致相同的INSERT语句失败,如下所示:

mysql> SET sql_mode='STRICT_ALL_TABLES,ERROR_FOR_DIVISION_BY_ZERO';
Query OK, 0 rows affected, 1 warning (0.00 sec)

mysql> INSERT INTO tn VALUES (NULL), (0), (1);
ERROR 1365 (22012): Division by 0

2.表的可访问性。有时,服务器SQL模式的更改会使分区表不可用。下面的CREATE TABLE语句只有在NO_UNSIGNED_SUBTRACTION模式生效:

mysql> SELECT @@sql_mode;
+------------+
| @@sql_mode |
+------------+
|            |
+------------+
1 row in set (0.00 sec)

mysql> CREATE TABLE tu (c1 BIGINT UNSIGNED)
    -> PARTITION BY RANGE(c1 - 10) (
    -> PARTITION p0 VALUES LESS THAN (-5),
    -> PARTITION p1 VALUES LESS THAN (0),
    -> PARTITION p2 VALUES LESS THAN (5),
    -> PARTITION p3 VALUES LESS THAN (10),
    -> PARTITION p4 VALUES LESS THAN (MAXVALUE)
    -> );
ERROR 1563 (HY000): Partition constant is out of partition function domain

mysql> SET sql_mode='NO_UNSIGNED_SUBTRACTION';
Query OK, 0 rows affected (0.01 sec)

mysql> SELECT @@sql_mode;
+-------------------------+
| @@sql_mode              |
+-------------------------+
| NO_UNSIGNED_SUBTRACTION |
+-------------------------+
1 row in set (0.00 sec)

mysql> CREATE TABLE tu (c1 BIGINT UNSIGNED)
    -> PARTITION BY RANGE(c1 - 10) (
    -> PARTITION p0 VALUES LESS THAN (-5),
    -> PARTITION p1 VALUES LESS THAN (0),
    -> PARTITION p2 VALUES LESS THAN (5),
    -> PARTITION p3 VALUES LESS THAN (10),
    -> PARTITION p4 VALUES LESS THAN (MAXVALUE)
    -> );
Query OK, 0 rows affected (0.02 sec)

如果在创建表后删除NO_UNSIGNED_SUBTRACTION服务器SQL模式,则可能无法再访问该表:

mysql> SET sql_mode='';
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT * FROM tu;
ERROR 1563 (HY000): Partition constant is out of partition function domain
mysql> INSERT INTO tu VALUES (20);
ERROR 1563 (HY000): Partition constant is out of partition function domain

服务器SQL模式也会影响分区表的复制。主服务器和从服务器上不同的SQL模式可能导致分区表达式的计算方式不同;这可能导致给定表的主副本 和从副本的分区数据分布不同,甚至可能导致在主副本上成功插入分区表的操作在从副本上失败。为了达到最佳效果,应该始终在主服务器和从 服务器上使用相同的服务器SQL模式。

性能考虑。下面列出了分区操作对性能的一些影响:
.文件系统操作。分区和重分区操作(ALTER TABLE
with PARTITION BY …, REORGANIZE PARTITION, or REMOVE PARTITIONING)依赖于文件系统操作的实现。这意味着这些操作的速度受到文件 系统类型和特征、磁盘速度、交换空间、操作系统的文件处理效率以及MySQL服务器选项和与文件处理相关的变量等因素的影响。尤其,大家应 该确保启用了large_files_support,并正确设置了open_files_limit。对于使用MyISAM存储引擎的分区表,增加myisam_max_sort_file_size可 以提高性能;通过启用innodb_file_per_table,涉及InnoDB表的分区和重分区操作可能会更加高效。

.MyISAM和分区文件描述符使用情况。对于分区的 MyISAM 表,MySQL 为每个分区使用 2 个文件描述符,对于每个处于打开状态的此类表都是如 此。这意味着在对分区的 MyISAM 表执行操作时,所需的文件描述符数量要比与之相同的未分区表多得多,尤其是在执行 ALTER TABLE 操作时 。

假设MyISAM表t有100个分区,例如下面的SQL语句创建的表:

mysql> CREATE TABLE t (c1 VARCHAR(50))
    -> ENGINE=MyISAM
    -> PARTITION BY KEY (c1) PARTITIONS 100;
Query OK, 0 rows affected, 1 warning (0.02 sec)

为简洁起见,我们对这个例子中显示的使用键分区表,但这里描述的文件描述符的使用适用于所有分区的MyISAM表,无论采用哪种分区类型。使 用其他存储引擎(如InnoDB)的分区表不受此影响。

现在假设您希望重新分区t,使它有101个分区,使用下面的语句:

mysql> ALTER TABLE t PARTITION BY KEY (c1) PARTITIONS 101;
Query OK, 0 rows affected, 1 warning (0.05 sec)
Records: 0  Duplicates: 0  Warnings: 1

为了处理ALTER TABLE语句,MySQL使用了402个文件描述符,即100个原始分区各2个,101个新分区各2个。这是因为在重组表数据期间,必须同 时打开所有分区(旧分区和新分区)。建议,如果您希望执行这样的操作,您应该确保——open-files-limit不要设置得太低,以容纳它们。

.表锁。通常,在表上执行分区操作的进程需要对表使用写锁。从这样的表中读取相对不受影响;挂起的INSERT和UPDATE操作在分区操作完成后 立即执行。

.存储引擎。分区操作、查询和更新操作通常在MyISAM表中比在InnoDB或NDB表中更快。

.索引;分区修剪。与非分区表一样,正确使用索引可以显著加快对分区表的查询速度。此外,设计分区表并对这些表进行查询以利用分区修剪可 以显著提高性能。

.加载数据的性能。在MySQL 5.7中,LOAD DATA使用缓冲来提高性能。您应该知道,缓冲区在每个分区中使用130 KB内存来实现这一点。

最大分区数。
对于一个给定的表,不使用NDB存储引擎的最大可能分区数是8192。这个数字包括子分区。

对于使用NDB存储引擎的表,用户定义的最大可能分区数是根据使用的NDB集群软件版本、数据节点数量等因素来确定的。

如果在创建具有大量分区(但小于最大分区数)的表时,您会遇到类似Got error … from storage engine: Out of resources
when opening file,可以通过增加open_files_limit系统变量的值来解决这个问题。但这取决于操作系统,在所有平台上可能不可行,也不可 取。在某些情况下,由于其他原因,使用大量(数百个)分区可能也是不可取的,因此使用更多分区并不会自动带来更好的结果。

不支持查询缓存。
对于分区表不支持查询缓存,并且对于涉及分区表的查询自动禁用查询缓存。不能为此类查询启用查询缓存。

按分区键缓存。
MySQL 5.7支持分区的MyISAM表的键缓存,在缓存语句中使用CACHE INDEX 和LOAD INDEX INTO CACHE。可以为一个、几个或所有分区定义键缓存 ,并且可以将一个、几个或所有分区的索引预装到键缓存中。

分区InnoDB表不支持外键。
使用InnoDB存储引擎的分区表不支持外键。更具体地说,这意味着以下两种说法是正确的:
1.使用用户定义的分区的InnoDB表的定义不能包含外键引用;定义中包含外键引用的InnoDB表不能被分区。

2.InnoDB表定义中不能包含用户分区表的外键引用;用户定义分区的InnoDB表不能包含外键引用的列。

刚才列出的限制范围包括所有使用InnoDB存储引擎的表。不允许CREATE TABLE和ALTER TABLE语句导致表违反这些限制。

ALTER TABLE … ORDER BY.ALTER TABLE…ORDER BY column语句对已分区的表运行会导致仅在每个分区内对行进行排序。

修改主键对REPLACE语句的影响。
在某些情况下可以修改表的主键。请注意,如果您的应用程序使用REPLACE语句,并且这样做,这些语句的结果可能会被彻底改变。

全文索引
分区表不支持全文索引或搜索,即使是使用InnoDB或MyISAM存储引擎的分区表。

空间列。具有空间数据类型(如点或几何)的列不能在分区表中使用。

临时表
临时表不能分区。(错误# 17497)

日志表
不能对日志表进行分区;ALTER TABLE … PARTITION BY …在这样的表上的语句失败并报错。

分区键的数据类型。
分区键必须是一个整数列或一个解析为整数的表达式。不能使用包含枚举列的表达式。列或表达式的值也可以是NULL。

这个限制有两个例外:
1.当按[LINEAR] KEY进行分区时,可以使用除TEXT或BLOB以外的任何有效MySQL数据类型的列作为分区键,因为MySQL内部的键散列函数会根据这 些类型生成正确的数据类型。例如,下面两个CREATE TABLE语句是有效的:

mysql> CREATE TABLE tkc (c1 CHAR)
    -> PARTITION BY KEY(c1)
    -> PARTITIONS 4;
Query OK, 0 rows affected (0.04 sec)

mysql> CREATE TABLE tkc (c1 CHAR)
    -> PARTITION BY KEY(c1)
    -> PARTITIONS 4;
Query OK, 0 rows affected (0.01 sec)

2.当按范围列或列表列分区时,可以使用string、DATE和DATETIME列。例如,下面的CREATE TABLE语句都是有效的。

mysql> CREATE TABLE rc (c1 INT, c2 DATE)
    -> PARTITION BY RANGE COLUMNS(c2) (
    -> PARTITION p0 VALUES LESS THAN('1990-01-01'),
    -> PARTITION p1 VALUES LESS THAN('1995-01-01'),
    -> PARTITION p2 VALUES LESS THAN('2000-01-01'),
    -> PARTITION p3 VALUES LESS THAN('2005-01-01'),
    -> PARTITION p4 VALUES LESS THAN(MAXVALUE)
    -> );
Query OK, 0 rows affected (0.02 sec)

mysql> CREATE TABLE lc (c1 INT, c2 CHAR(1))
    -> PARTITION BY LIST COLUMNS(c2) (
    -> PARTITION p0 VALUES IN('a', 'd', 'g', 'j', 'm', 'p', 's', 'v', 'y'),
    -> PARTITION p1 VALUES IN('b', 'e', 'h', 'k', 'n', 'q', 't', 'w', 'z'),
    -> PARTITION p2 VALUES IN('c', 'f', 'i', 'l', 'o', 'r', 'u', 'x', NULL)
    -> );
Query OK, 0 rows affected (0.03 sec)

上述两种异常都不适用于BLOB或TEXT列类型。

子查询
分区键可能不是子查询,即使该子查询解析为整数值或NULL。

子分区的问题。
子分区必须使用散列或键分区。只有范围分区和列表分区可以分区。哈希分区和键分区不能分区。

SUBPARTITION BY KEY要求显式指定子分区的列或列s,不像按键分区的情况,可以省略(默认使用表的主键列)。考虑下面这条语句创建的表:

mysql> CREATE TABLE ts (
    -> id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    -> name VARCHAR(30)
    -> );
Query OK, 0 rows affected (0.01 sec)

你可以创建一个具有相同列的表,并按KEY进行分区,使用如下语句:

mysql> drop table ts;
Query OK, 0 rows affected (0.01 sec)

mysql> CREATE TABLE ts (
    -> id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    -> name VARCHAR(30)
    -> )
    -> PARTITION BY KEY()
    -> PARTITIONS 4;
Query OK, 0 rows affected (0.02 sec)

前面的语句被看作是这样写的,表的主键列被用作分区列:

mysql> drop table ts;
Query OK, 0 rows affected (0.01 sec)

mysql> CREATE TABLE ts (
    -> id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    -> name VARCHAR(30)
    -> )
    -> PARTITION BY KEY(id)
    -> PARTITIONS 4;
Query OK, 0 rows affected (0.01 sec)

但是,下面的语句尝试使用默认列作为子分区列创建子分区表失败,并且必须指定该列才能成功,如下所示:

mysql> CREATE TABLE ts (
    -> id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    -> name VARCHAR(30)
    -> )
    -> PARTITION BY RANGE(id)
    -> SUBPARTITION BY KEY()
    -> SUBPARTITIONS 4
    -> (
    -> PARTITION p0 VALUES LESS THAN (100),
    -> PARTITION p1 VALUES LESS THAN (MAXVALUE)
    -> );
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for  the right syntax to use near ')
SUBPARTITIONS 4
(
PARTITION p0 VALUES LESS THAN (100),
PARTITION p1 VALUES LES' at line 6


mysql> CREATE TABLE ts (
    -> id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    -> name VARCHAR(30)
    -> )
    -> PARTITION BY RANGE(id)
    -> SUBPARTITION BY KEY(id)
    -> SUBPARTITIONS 4
    -> (
    -> PARTITION p0 VALUES LESS THAN (100),
    -> PARTITION p1 VALUES LESS THAN (MAXVALUE)
    -> );
Query OK, 0 rows affected (0.03 sec)

这是一个已知的问题(见Bug #51470)。

数据目录和索引目录选项。
当与分区表一起使用时,DATA DIRECTORY和INDEX DIRECTORY受到以下限制:
.表级数据目录和索引目录选项被忽略(见Bug #32091)。

.在Windows上,MyISAM表的单个分区或子分区不支持数据目录和索引目录选项。但是,你可以将数据目录用于InnoDB表的单个分区或子分区。

修复和重建分区表。
对于已分区的表,支持CHECK TABLE、OPTIMIZE TABLE、ANALYZE TABLE、REPAIR TABLE语句。

此外,用户还可以使用ALTER TABLE … REBUILD PARTITION重建分区表的一个或多个分区;ALTER TABLE … REORGANIZE PARTITION也会导致 重新构建分区。
从MySQL 5.7.2开始,在子分区中支持ANALYZE, CHECK, OPTIMIZE, REPAIR和TRUNCATE操作。在MySQL 5.7.5之前,REBUILD也被接受,尽管这 没有影响。

分区表不支持Mysqlcheck、myisamchk和myisampack。

导出选项(刷新表)。

在MySQL 5.7.4及更早版本中,不支持FLUSH TABLES语句的FOR EXPORT选项。(Bug# 16943907)

MySQL 5.7 分区选择

分区选择
MySQL 5.7支持显式选择分区和子分区,当执行语句时,应该检查是否符合给定的WHERE条件。分区选择与分区修剪类似,只检查特定的分区是否 匹配,但在两个关键方面有所不同。
1.要检查的分区由语句的发布者指定,这与自动进行分区修剪不同

2.虽然分区修剪仅适用于查询,但查询和许多DML语句都支持显式选择分区。

下面列出了支持显式分区选择的SQL语句:

SELECT
DELETE
INSERT
REPLACE
UPDATE
LOAD DATA.
LOAD XML.

显式分区选择是使用partition选项实现的。对于所有支持的语句,该选项使用如下所示的语法:

PARTITION (partition_names)
partition_names:
partition_name, ...

此选项始终跟随分区所属的表的名称。partition_names是要使用的分区或子分区的列表,用逗号分隔。该列表中的每个名称都必须是指定表的 现有分区或子分区的名称;如果没有找到任何分区或子分区,则语句失败并报错(partition ‘partition_name’ doesn’t exist)。以partition_names命名的分区和子分区可以以任意顺序列出,也可以重叠。

当使用PARTITION选项时,只检查列出的分区和子分区是否匹配行。这个选项可以在SELECT语句中使用,以确定哪些行属于给定的分区。
考虑一个名为employees的分区表,使用下面的语句创建和填充:

mysql> CREATE TABLE employees (
    -> id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    -> fname VARCHAR(25) NOT NULL,
    -> lname VARCHAR(25) NOT NULL,
    -> store_id INT NOT NULL,
    -> department_id INT NOT NULL
    -> )
    -> PARTITION BY RANGE(id) (
    -> PARTITION p0 VALUES LESS THAN (5),
    -> PARTITION p1 VALUES LESS THAN (10),
    -> PARTITION p2 VALUES LESS THAN (15),
    -> PARTITION p3 VALUES LESS THAN MAXVALUE
    -> );
Query OK, 0 rows affected (0.02 sec)

mysql> INSERT INTO employees VALUES
    -> ('', 'Bob', 'Taylor', 3, 2), ('', 'Frank', 'Williams', 1, 2),
    -> ('', 'Ellen', 'Johnson', 3, 4), ('', 'Jim', 'Smith', 2, 4),
    -> ('', 'Mary', 'Jones', 1, 1), ('', 'Linda', 'Black', 2, 3),
    -> ('', 'Ed', 'Jones', 2, 1), ('', 'June', 'Wilson', 3, 1),
    -> ('', 'Andy', 'Smith', 1, 3), ('', 'Lou', 'Waters', 2, 4),
    -> ('', 'Jill', 'Stone', 1, 4), ('', 'Roger', 'White', 3, 2),
    -> ('', 'Howard', 'Andrews', 1, 2), ('', 'Fred', 'Goldberg', 3, 3),
    -> ('', 'Barbara', 'Brown', 2, 3), ('', 'Alice', 'Rogers', 2, 2),
    -> ('', 'Mark', 'Morgan', 3, 3), ('', 'Karen', 'Cole', 3, 2);
Query OK, 18 rows affected, 18 warnings (0.00 sec)
Records: 18  Duplicates: 0  Warnings: 18

您可以看到哪些行存储在分区p1中,如下所示:

mysql> SELECT * FROM employees PARTITION (p1);
+----+-------+--------+----------+---------------+
| id | fname | lname  | store_id | department_id |
+----+-------+--------+----------+---------------+
|  5 | Mary  | Jones  |        1 |             1 |
|  6 | Linda | Black  |        2 |             3 |
|  7 | Ed    | Jones  |        2 |             1 |
|  8 | June  | Wilson |        3 |             1 |
|  9 | Andy  | Smith  |        1 |             3 |
+----+-------+--------+----------+---------------+
5 rows in set (0.00 sec)

结果与查询SELECT * FROM employees WHERE id BETWEEN 5 AND 9得到的结果相同。

若要从多个分区中获取行,请将其名称作为逗号分隔的列表提供。例如,SELECT * FROM employees PARTITION (p1, p2)返回p1和p2分区的所 有行,不包括其他分区的行。

可以使用PARTITION选项重写针对分区表的任何有效查询,以将结果限制为一个或多个所需分区。您可以使用WHERE条件、ORDER BY和LIMIT选项 ,等等。还可以使用带有HAVING和GROUP BY选项的聚合函数。下面每个查询在前面定义的employees表上运行时会产生一个有效的结果:

mysql> SELECT * FROM employees PARTITION (p0, p2) WHERE lname LIKE 'S%';
+----+-------+-------+----------+---------------+
| id | fname | lname | store_id | department_id |
+----+-------+-------+----------+---------------+
|  4 | Jim   | Smith |        2 |             4 |
| 11 | Jill  | Stone |        1 |             4 |
+----+-------+-------+----------+---------------+
2 rows in set (0.00 sec)

mysql> SELECT id, CONCAT(fname, ' ', lname) AS name FROM employees PARTITION (p0) ORDER BY lname;
+----+----------------+
| id | name           |
+----+----------------+
|  3 | Ellen Johnson  |
|  4 | Jim Smith      |
|  1 | Bob Taylor     |
|  2 | Frank Williams |
+----+----------------+
4 rows in set (0.00 sec)
mysql> SELECT store_id, COUNT(department_id) AS c FROM employees PARTITION (p1,p2,p3) GROUP BY store_id HAVING c > 4;
+----------+---+
| store_id | c |
+----------+---+
|        2 | 5 |
|        3 | 5 |
+----------+---+
2 rows in set (0.00 sec)

您也可以在INSERT…SELECT的SELECT部分使用PARTITION选项,如下所示:

mysql> CREATE TABLE employees_copy LIKE employees;
Query OK, 0 rows affected (0.02 sec)

mysql> INSERT INTO employees_copy SELECT * FROM employees PARTITION (p2);
Query OK, 5 rows affected (0.00 sec)
Records: 5  Duplicates: 0  Warnings: 0


mysql> SELECT * FROM employees_copy;
+----+--------+----------+----------+---------------+
| id | fname  | lname    | store_id | department_id |
+----+--------+----------+----------+---------------+
| 10 | Lou    | Waters   |        2 |             4 |
| 11 | Jill   | Stone    |        1 |             4 |
| 12 | Roger  | White    |        3 |             2 |
| 13 | Howard | Andrews  |        1 |             2 |
| 14 | Fred   | Goldberg |        3 |             3 |
+----+--------+----------+----------+---------------+
5 rows in set (0.00 sec)

分区选择也可以用于连接。假设我们使用下面的语句创建并填充两个表:

mysql> CREATE TABLE stores (
    -> id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    -> city VARCHAR(30) NOT NULL
    -> )
    -> PARTITION BY HASH(id)
    -> PARTITIONS 2;
Query OK, 0 rows affected (0.01 sec)

mysql> INSERT INTO stores VALUES
    -> ('', 'Nambucca'), ('', 'Uranga'),
    -> ('', 'Bellingen'), ('', 'Grafton');
Query OK, 4 rows affected, 4 warnings (0.00 sec)
Records: 4  Duplicates: 0  Warnings: 4

mysql> CREATE TABLE departments (
    -> id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    -> name VARCHAR(30) NOT NULL
    -> )
    -> PARTITION BY KEY(id)
    -> PARTITIONS 2;
Query OK, 0 rows affected (0.01 sec)

mysql> INSERT INTO departments VALUES
    -> ('', 'Sales'), ('', 'Customer Service'),
    -> ('', 'Delivery'), ('', 'Accounting');
Query OK, 4 rows affected, 4 warnings (0.00 sec)
Records: 4  Duplicates: 0  Warnings: 4

您可以显式地从连接中的任何或所有表中选择分区(或子分区,或两者都选择)。(PARTITION选项用于从给定表中选择分区,该选项紧跟在表名 之后,位于所有其他选项(包括任何表别名)之前。例如,下面的查询获取在Nambucca和Bellingen两个城市(stores表的分区p0)的商店中 Sales或Delivery部门(departments表的分区p1)工作的所有员工的姓名、员工ID、部门和城市:

mysql> SELECT
    -> e.id AS 'Employee ID', CONCAT(e.fname, ' ', e.lname) AS Name,
    -> s.city AS City, d.name AS department
    -> FROM employees AS e
    -> JOIN stores PARTITION (p1) AS s ON e.store_id=s.id
    -> JOIN departments PARTITION (p0) AS d ON e.department_id=d.id
    -> ORDER BY e.lname;
+-------------+---------------+-----------+------------+
| Employee ID | Name          | City      | department |
+-------------+---------------+-----------+------------+
|          14 | Fred Goldberg | Bellingen | Delivery   |
|           5 | Mary Jones    | Nambucca  | Sales      |
|          17 | Mark Morgan   | Bellingen | Delivery   |
|           9 | Andy Smith    | Nambucca  | Delivery   |
|           8 | June Wilson   | Bellingen | Sales      |
+-------------+---------------+-----------+------------+
5 rows in set (0.00 sec)

当PARTITION选项与DELETE语句一起使用时,只有使用该选项列出的那些分区(和子分区,如果有的话)才会检查要删除的行。任何其他分区都 会被忽略,如下所示:

mysql> SELECT * FROM employees WHERE fname LIKE 'j%';
+----+-------+--------+----------+---------------+
| id | fname | lname  | store_id | department_id |
+----+-------+--------+----------+---------------+
|  4 | Jim   | Smith  |        2 |             4 |
|  8 | June  | Wilson |        3 |             1 |
| 11 | Jill  | Stone  |        1 |             4 |
+----+-------+--------+----------+---------------+
3 rows in set (0.00 sec)

mysql> DELETE FROM employees PARTITION (p0, p1) WHERE fname LIKE 'j%';
Query OK, 2 rows affected (0.00 sec)

mysql> SELECT * FROM employees WHERE fname LIKE 'j%';
+----+-------+-------+----------+---------------+
| id | fname | lname | store_id | department_id |
+----+-------+-------+----------+---------------+
| 11 | Jill  | Stone |        1 |             4 |
+----+-------+-------+----------+---------------+
1 row in set (0.00 sec)

只有分区p0和p1中匹配WHERE条件的两行被删除。从第二次运行SELECT时的结果中可以看到,表中仍然有一行与WHERE条件,但驻留在不同的分区 (p2)。

使用显式分区选择的UPDATE语句的行为方式相同;在确定要更新的行时,只考虑由PARTITION选项引用的分区中的行,可以通过执行以下语句看 到:

mysql> UPDATE employees PARTITION (p0) SET store_id = 2 WHERE fname = 'Jill';
Query OK, 0 rows affected (0.00 sec)
Rows matched: 0  Changed: 0  Warnings: 0

mysql> SELECT * FROM employees WHERE fname = 'Jill';
+----+-------+-------+----------+---------------+
| id | fname | lname | store_id | department_id |
+----+-------+-------+----------+---------------+
| 11 | Jill  | Stone |        1 |             4 |
+----+-------+-------+----------+---------------+
1 row in set (0.00 sec)

mysql> UPDATE employees PARTITION (p2) SET store_id = 2 WHERE fname = 'Jill';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> SELECT * FROM employees WHERE fname = 'Jill';
+----+-------+-------+----------+---------------+
| id | fname | lname | store_id | department_id |
+----+-------+-------+----------+---------------+
| 11 | Jill  | Stone |        2 |             4 |
+----+-------+-------+----------+---------------+
1 row in set (0.00 sec)

同样,当PARTITION与DELETE一起使用时,只检查分区或分区列表中指定的分区中的行是否删除。

对于插入行的语句,其行为的不同之处在于,未能找到合适的分区将导致语句失败。对于INSERT和REPLACE语句都是如此,如下所示:

mysql> INSERT INTO employees PARTITION (p2) VALUES (20, 'Jan', 'Jones', 1, 3);
ERROR 1748 (HY000): Found a row not matching the given partition set
mysql> INSERT INTO employees PARTITION (p3) VALUES (20, 'Jan', 'Jones', 1, 3);
Query OK, 1 row affected (0.00 sec)

mysql> REPLACE INTO employees PARTITION (p0) VALUES (20, 'Jan', 'Jones', 3, 2);
ERROR 1748 (HY000): Found a row not matching the given partition set
mysql> REPLACE INTO employees PARTITION (p3) VALUES (20, 'Jan', 'Jones', 3, 2);
Query OK, 2 rows affected (0.01 sec)

对于在使用InnoDB存储引擎的分区表中写多行数据的语句:如果下列列表中的任何一行不能写入partition_names列表中指定的任何一个分区, 则整个语句失败,不会写入任何行。下面的例子展示了如何使用INSERT语句,重用了之前创建的employees表:

mysql> INSERT INTO employees PARTITION (p3, p4) VALUES (24, 'Tim', 'Greene', 3, 1), (26, 'Linda', 'Mills', 2, 1);
ERROR 1748 (HY000): Found a row not matching the given partition set

mysql> INSERT INTO employees PARTITION (p3, p4,p5) VALUES (24, 'Tim', 'Greene', 3, 1), (26, 'Linda', 'Mills', 2, 1);
Query OK, 2 rows affected (0.00 sec)
Records: 2  Duplicates: 0  Warnings: 0

对于写入多行的INSERT语句和REPLACE语句,上述情况都成立。

在MySQL 5.7.1及更高版本中,对于使用提供自动分区的存储引擎(如NDB)的表,禁用分区选择。(错误# 14827952)

MySQL 5.7 分区修剪

分区修剪
分区修剪背后的核心概念相对简单,可以描述为“不扫描可能没有匹配值的分区 ”。假设你有一个由以下语句定义的分区表t1:

mysql> CREATE TABLE t1 (
    -> fname VARCHAR(50) NOT NULL,
    -> lname VARCHAR(50) NOT NULL,
    -> region_code TINYINT UNSIGNED NOT NULL,
    -> dob DATE NOT NULL
    -> )
    -> PARTITION BY RANGE( region_code ) (
    -> PARTITION p0 VALUES LESS THAN (64),
    -> PARTITION p1 VALUES LESS THAN (128),
    -> PARTITION p2 VALUES LESS THAN (192),
    -> PARTITION p3 VALUES LESS THAN MAXVALUE
    -> );
Query OK, 0 rows affected (0.02 sec)

考虑这样一种情况,您希望从SELECT语句中获得如下结果:

SELECT fname, lname, region_code, dob
FROM t1
WHERE region_code > 125 AND region_code < 130;

很容易看出,应该返回的行都不会出现在p0或p3分区中;也就是说,我们只需要在分区p1和p2中查找匹配的行。这样,查找匹配的行所花费的时 间和精力就比扫描表中的所有分区要少得多。这种“删除”不需要的分区称为剪枝(pruning)。当优化器可以在执行此查询时使用分区修剪时 ,查询的执行速度可以比针对包含相同列定义和数据的未分区表的相同查询快一个数量级。

在对已分区的MyISAM表进行修剪时,不管是否检查分区,都会打开所有分区,这取决于MyISAM存储引擎的设计。这意味着用户必须有足够数量的 文件描述符来覆盖表的所有分区。

此限制不适用于使用其他MySQL存储引擎(如InnoDB)的分区表。

只要WHERE条件可以简化为以下两种情况之一,优化器就可以进行修剪。
.partition_column = constant

.partition_column IN (constant1, constant2, ..., constantN)

在第一种情况下,优化器只是计算给定值的分区表达式,确定哪个分区包含该值,然后只扫描这个分区。在很多情况下,等号可以替换为其他算 术比较,包括< 、>、< =、>=和<>。在WHERE子句中使用BETWEEN的一些查询也可以利用分区修剪。

在第二种情况下,优化器为列表中的每个值计算分区表达式,创建一个匹配分区的列表,然后只扫描这个分区列表中的分区。

MySQL可以对SELECT、DELETE和UPDATE语句进行分区修剪。INSERT语句目前不能被修剪。

修剪也可以应用于短范围,优化器可以将其转换为等效的值列表。例如,在前面的例子中,WHERE子句可以转换为WHERE region_code in(126, 127, 128, 129)。然后,优化器可以确定列表中的前两个值在分区p1中找到,其余两个值在分区p2中找到,并且其他分区不包含相关值,因此 不需要搜索匹配的行。

对于使用范围列或列表列分区的表,如果条件涉及对多个列进行上述类型的比较,优化器还可以执行修剪。

只要分区表达式包含一个等式或一个可以简化为一组等式的范围,或者分区表达式表示一个递增或递减关系,就可以应用这种类型的优化。当分 区表达式使用YEAR()或TO_DAYS()函数时,还可以对在DATE或DATETIME列上分区的表进行剪枝。此外,在MySQL 5.7中,当分区表达式使用
TO_SECONDS()函数。

假设表t2,定义如下,在一个DATE列上分区:

mysql> CREATE TABLE t2 (
    -> fname VARCHAR(50) NOT NULL,
    -> lname VARCHAR(50) NOT NULL,
    -> region_code TINYINT UNSIGNED NOT NULL,
    -> dob DATE NOT NULL
    -> )
    -> PARTITION BY RANGE( YEAR(dob) ) (
    -> PARTITION d0 VALUES LESS THAN (1970),
    -> PARTITION d1 VALUES LESS THAN (1975),
    -> PARTITION d2 VALUES LESS THAN (1980),
    -> PARTITION d3 VALUES LESS THAN (1985),
    -> PARTITION d4 VALUES LESS THAN (1990),
    -> PARTITION d5 VALUES LESS THAN (2000),
    -> PARTITION d6 VALUES LESS THAN (2005),
    -> PARTITION d7 VALUES LESS THAN MAXVALUE
    -> );
Query OK, 0 rows affected (0.02 sec)

以下使用t2的语句可以利用分区修剪:

SELECT * FROM t2 WHERE dob = '1982-06-23';

UPDATE t2 SET region_code = 8 WHERE dob BETWEEN '1991-02-15' AND '1997-04-25';

DELETE FROM t2 WHERE dob >= '1984-06-21' AND dob < = '1999-06-21'

最后一条语句,优化器也可以这样做:
1.找到包含范围下限的分区。
YEAR('1984-06-21')产生值1984,该值在分区d3中找到。

2.找到包含范围高端的分区。
YEAR('1999-06-21')的计算结果为1999,位于分区d5。

3.只扫描这两个分区以及它们之间的任何分区。
在本例中,这意味着只扫描分区d3、d4和d5。剩余的分区可以被安全地忽略(并且被忽略)。

在针对分区表的语句的WHERE条件中引用的无效DATE和DATETIME值将被视为NULL。这意味着像SELECT * FROM partitioned_table WHERE date_column < ‘2008-12-00’这样的查询不返回任何值(见Bug #40972)。 到目前为止,我们只查看了使用范围分区的示例,但是修剪也可以应用于其他分区类型。 考虑一个按列表分区的表,其中分区表达式是递增或递减的,例如这里展示的表t3。(在这个例子中,为了简洁,我们假设region_code列的值 被限制在1到10之间,包括1和10。)

mysql> CREATE TABLE t3 (
    -> fname VARCHAR(50) NOT NULL,
    -> lname VARCHAR(50) NOT NULL,
    -> region_code TINYINT UNSIGNED NOT NULL,
    -> dob DATE NOT NULL
    -> )
    -> PARTITION BY LIST(region_code) (
    -> PARTITION r0 VALUES IN (1, 3),
    -> PARTITION r1 VALUES IN (2, 5, 8),
    -> PARTITION r2 VALUES IN (4, 9),
    -> PARTITION r3 VALUES IN (6, 7, 10)
    -> );
Query OK, 0 rows affected (0.02 sec)

对于SELECT * FROM t3 WHERE region_code BETWEEN 1 AND 3这样的语句,优化器确定在哪些分区中可以找到值1、2和3 (r0和r1),并跳过其余 的分区(r2和r3)

对于按哈希或线性键分区的表,当WHERE子句对分区表达式中使用的列使用简单=关系时,也可以进行分区修剪。考虑这样创建一个表:

mysql> CREATE TABLE t4 (
    -> fname VARCHAR(50) NOT NULL,
    -> lname VARCHAR(50) NOT NULL,
    -> region_code TINYINT UNSIGNED NOT NULL,
    -> dob DATE NOT NULL
    -> )
    -> PARTITION BY KEY(region_code)
    -> PARTITIONS 8;
Query OK, 0 rows affected (0.03 sec)

将列值与常量进行比较的语句可以被修剪:

UPDATE t4 WHERE region_code = 7;

修剪也可以用于短范围,因为优化器可以将这种条件转化为关系。例如,使用前面定义的表t4,可以对下列查询进行剪枝:

SELECT * FROM t4 WHERE region_code > 2 AND region_code < 6;

SELECT * FROM t4 WHERE region_code BETWEEN 3 AND 5;

在这两种情况下,优化器将WHERE子句转换为WHERE region_code In (3, 4, 5)。

只有当范围大小小于分区数量时,才会使用这种优化。想想这个说法:
DELETE FROM t4 WHERE region_code BETWEEN 4 AND 12;

WHERE子句中的范围包含9个值(4、5、6、7、8、9、10、11、12),但是t4只有8个分区。这意味着DELETE语句不能修剪。

当表按按哈希或线性键分区时,剪枝只能用于整型列。例如,下面的语句不能使用剪枝,因为dob是日期列:

SELECT * FROM t4 WHERE dob >= '2001-04-14' AND dob < = '2005-10-15';

但是,如果表的年份存储在INT列中,那么WHERE year_col >= 2001和year_col <= 2005的查询就会被删除。

MySQL 5.7 获取分区信息

获取分区信息
获取有关现有分区的信息,这可以通过多种方式完成。获取此类信息的方法包括:
.使用SHOW CREATE TABLE语句查看创建分区表时使用的分区子句。

.使用SHOW TABLE STATUS语句确定表是否被分区。

.查询INFORMATION_SCHEMA.PARTITIONS表。

.使用EXPLAIN SELECT语句查看给定SELECT使用了哪些分区。

正如本章其他地方所讨论的,SHOW CREATE TABLE在其输出中包含用于创建分区表的PARTITION BY子句。例如:

mysql> SHOW CREATE TABLE trb3\G
*************************** 1. row ***************************
       Table: trb3
Create Table: CREATE TABLE `trb3` (
  `id` int(11) DEFAULT NULL,
  `name` varchar(50) DEFAULT NULL,
  `purchased` date DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
/*!50100 PARTITION BY KEY (id)
PARTITIONS 2 */
1 row in set (0.01 sec)

对于已分区表,SHOW TABLE STATUS的输出与未分区表的输出相同,只是Create_options列包含字符串partitioned。Engine列包含表的所有分区 使用的存储引擎的名称。

您还可以从INFORMATION_SCHEMA中获取有关分区的信息,它包含一个PARTITIONS表。

使用EXPLAIN可以确定给定的SELECT查询涉及分区表的哪些分区。EXPLAIN输出中的partitions列列出了将从匹配的分区中查询记录。

假设您创建了一个表trb1,并按如下方式填充:

mysql> CREATE TABLE trb1 (id INT, name VARCHAR(50), purchased DATE)
    -> PARTITION BY RANGE(id)
    -> (
    -> PARTITION p0 VALUES LESS THAN (3),
    -> PARTITION p1 VALUES LESS THAN (7),
    -> PARTITION p2 VALUES LESS THAN (9),
    -> PARTITION p3 VALUES LESS THAN (11)
    -> );
Query OK, 0 rows affected (0.02 sec)

mysql> INSERT INTO trb1 VALUES
    -> (1, 'desk organiser', '2003-10-15'),
    -> (2, 'CD player', '1993-11-05'),
    -> (3, 'TV set', '1996-03-10'),
    -> (4, 'bookcase', '1982-01-10'),
    -> (5, 'exercise bike', '2004-05-09'),
    -> (6, 'sofa', '1987-06-05'),
    -> (7, 'popcorn maker', '2001-11-22'),
    -> (8, 'aquarium', '1992-08-04'),
    -> (9, 'study desk', '1984-09-16'),
    -> (10, 'lava lamp', '1998-12-25');
Query OK, 10 rows affected (0.00 sec)
Records: 10  Duplicates: 0  Warnings: 0

您可以看到在SELECT * FROM trb1;这样的查询中使用了哪些分区,如下所示:

mysql> EXPLAIN SELECT * FROM trb1\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: trb1
   partitions: p0,p1,p2,p3
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 10
     filtered: 100.00
        Extra: NULL
1 row in set, 1 warning (0.00 sec)

在本例中,搜索所有四个分区。然而,当在查询中添加使用分区键的限制条件时,您可以看到只有那些包含匹配值的分区才会被扫描,如下所示:

mysql> EXPLAIN SELECT * FROM trb1 WHERE id < 5\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: trb1
   partitions: p0,p1
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 6
     filtered: 33.33
        Extra: Using where
1 row in set, 1 warning (0.00 sec)

EXPLAIN还提供了使用的键和可能的键的信息:

mysql> ALTER TABLE trb1 ADD PRIMARY KEY (id);
Query OK, 0 rows affected (0.13 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> EXPLAIN SELECT * FROM trb1 WHERE id < 5\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: trb1
   partitions: p0,p1
         type: range
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 4
          ref: NULL
         rows: 4
     filtered: 100.00
        Extra: Using where
1 row in set, 1 warning (0.00 sec)

如果使用EXPLAIN分区来检查针对未分区表的查询,则不会产生错误,但PARTITIONS列的值始终为NULL。EXPLAIN输出的rows列显示表中的总行数 。

MySQL 5.7 分区维护

分区维护
在MySQL 5.7中,许多表和分区维护任务可以使用SQL语句在分区表上执行。

对分区表的维护可以使用CHECK TABLE,OPTIMIZE TABLE, ANALYZE TABLE和REPAIR TABLE,它们支持分区表。

用户可以使用ALTER TABLE的一些扩展来直接在一个或多个分区上执行这种类型的操作,如下所示:
.重建分区。重建分区;这与删除存储在分区中的所有记录,然后重新插入它们具有相同的效果。这对于碎片整理很有用。
例如:

ALTER TABLE t1 REBUILD PARTITION p0, p1;

.优化分区。如果你从一个分区中删除了大量的行,或者你对一个有变长度行(即有VARCHAR、BLOB或TEXT列)的分区表做了很多修改,你可以使 用ALTER TABLE … OPTIMIZE PARTITION以回收任何未使用的空间并整理分区数据文件。
例如:

ALTER TABLE t1 OPTIMIZE PARTITION p0, p1;

在一个给定分区上使用OPTIMIZE PARTITION,等价于在该分区上运行CHECK PARTITION,ANALYZE PARTITION和REPAIR PARTITION。

一些MySQL存储引擎,包括InnoDB,不支持单个分区优化;在这种情况下,ALTER TABLE … OPTIMIZE PARTITION会分析并重建整个表,并发出 适当的警告。(Bug #11751825, Bug #42822)使用ALTER TABLE … REBUILD PARTITION和ALTER TABLE … ANALYZE PARTITION 避免这个问题 。

.分析分区。这将读取并存储分区的键分布。
例如:
ALTER TABLE t1 ANALYZE PARTITION p3;

.修复分区。这将修复损坏的分区。
例如:

ALTER TABLE t1 REPAIR PARTITION p0,p1;

通常,当分区包含重复的键错误时,REPAIR PARTITION会失败。在MySQL 5.7.2及更高版本中,你可以使用ALTER IGNORE TABLE这个选项,在这 种情况下,所有因为重复键而无法移动的行都会从分区中移除(Bug #16900947)。

.检查分区。您可以检查分区是否有错误,其方式与对未分区的表使用CHECK TABLE的方式大致相同。
例如:

ALTER TABLE trb3 CHECK PARTITION p1;

这个命令将告诉您表t1的分区p1中的数据或索引是否损坏。如果是这种情况,请使用ALTER TABLE…REPAIR PARTITION修复分区。

正常情况下,当分区包含重复的键错误时,CHECK PARTITION会失败。在MySQL 5.7.2及更高版本中,你可以使用ALTER IGNORE TABLE这个选项, 在这种情况下,该语句将返回发现违反重复键的分区中每一行的内容。只报告表的分区表达式中列的值。(错误# 16900947)
刚才显示的列表中的每个语句还支持关键字ALL来代替分区名称列表。使用ALL会导致语句作用于表中的所有分区。

mysqlcheck和myisamchk对分区表不支持。

在MySQL 5.7中,你也可以使用ALTER TABLE … TRUNCATE PARTITION。这条语句可以用来删除一个或多个分区中的所有行,其方式与TRUNCATE TABLE删除表中的所有行差不多。

ALTER TABLE…TRUNCATE PARTITION ALL截断表中的所有分区。

MySQL 5.7 用非分区表交换子分区

用非分区表交换子分区
你也可以执行ALTER TABLE … EXCHANGE PARTITION语句使用一个未分区表来交换一个已分区表的子分区。在下面的例子中,我们首先创建一个 表es,它按范围分区,并按键进行子分区,然后像对表e那样填充这个表,然后创建一个空的、无分区的表es2副本,如下所示:

mysql> CREATE TABLE es (
    -> id INT NOT NULL,
    -> fname VARCHAR(30),
    -> lname VARCHAR(30)
    -> )
    -> PARTITION BY RANGE (id)
    -> SUBPARTITION BY KEY (lname)
    -> SUBPARTITIONS 2 (
    -> PARTITION p0 VALUES LESS THAN (50),
    -> PARTITION p1 VALUES LESS THAN (100),
    -> PARTITION p2 VALUES LESS THAN (150),
    -> PARTITION p3 VALUES LESS THAN (MAXVALUE)
    -> );
Query OK, 0 rows affected (0.03 sec)

mysql> INSERT INTO es VALUES
    -> (1669, "Jim", "Smith"),
    -> (337, "Mary", "Jones"),
    -> (16, "Frank", "White"),
    -> (2005, "Linda", "Black");
Query OK, 4 rows affected (0.00 sec)
Records: 4  Duplicates: 0  Warnings: 0

mysql> CREATE TABLE es2 LIKE es;
Query OK, 0 rows affected (0.04 sec)

mysql> ALTER TABLE es2 REMOVE PARTITIONING;
Query OK, 0 rows affected (0.03 sec)
Records: 0  Duplicates: 0  Warnings: 0

尽管在创建表时我们没有显式地命名任何子分区,但是我们可以通过查询INFORMATION_SCHEMA。PARTITIONS表的UBPARTITION_NAME来获得这些生 成的名称,如下所示:

mysql> SELECT PARTITION_NAME, SUBPARTITION_NAME, TABLE_ROWS  FROM INFORMATION_SCHEMA.PARTITIONS  WHERE TABLE_NAME = 'es';
+----------------+-------------------+------------+
| PARTITION_NAME | SUBPARTITION_NAME | TABLE_ROWS |
+----------------+-------------------+------------+
| p0             | p0sp0             |          1 |
| p0             | p0sp1             |          0 |
| p1             | p1sp0             |          0 |
| p1             | p1sp1             |          0 |
| p2             | p2sp0             |          0 |
| p2             | p2sp1             |          0 |
| p3             | p3sp0             |          3 |
| p3             | p3sp1             |          0 |
+----------------+-------------------+------------+
8 rows in set (0.00 sec)

下面的ALTER TABLE语句将子分区p3sp0表es与未分区表es2交换:

mysql> ALTER TABLE es EXCHANGE PARTITION p3sp0 WITH TABLE es2;
Query OK, 0 rows affected (0.01 sec)

您可以通过发出以下查询来验证行是否被交换:

mysql> SELECT PARTITION_NAME, SUBPARTITION_NAME, TABLE_ROWS  FROM INFORMATION_SCHEMA.PARTITIONS  WHERE TABLE_NAME = 'es';
+----------------+-------------------+------------+
| PARTITION_NAME | SUBPARTITION_NAME | TABLE_ROWS |
+----------------+-------------------+------------+
| p0             | p0sp0             |          1 |
| p0             | p0sp1             |          0 |
| p1             | p1sp0             |          0 |
| p1             | p1sp1             |          0 |
| p2             | p2sp0             |          0 |
| p2             | p2sp1             |          0 |
| p3             | p3sp0             |          0 |
| p3             | p3sp1             |          0 |
+----------------+-------------------+------------+
8 rows in set (0.01 sec)

mysql> SELECT * FROM es2;
+------+-------+-------+
| id   | fname | lname |
+------+-------+-------+
| 1669 | Jim   | Smith |
|  337 | Mary  | Jones |
| 2005 | Linda | Black |
+------+-------+-------+
3 rows in set (0.01 sec)

如果一个表被子分区了,你只能用一个未分区的表交换表的子分区,而不是整个分区,如下所示:

mysql> ALTER TABLE es EXCHANGE PARTITION p3 WITH TABLE es2;
ERROR 1734 (HY000): Subpartitioned table, use subpartition instead of partition

MySQL使用的表结构的比较是非常严格的。分区表和非分区表的列和索引的数量、顺序、名称和类型必须完全匹配。另外,两个表必须使用相同 的存储引擎:

mysql> CREATE TABLE es3 LIKE e;
Query OK, 0 rows affected (0.02 sec)

mysql> ALTER TABLE es3 REMOVE PARTITIONING;
Query OK, 0 rows affected (0.03 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> SHOW CREATE TABLE es3\G
*************************** 1. row ***************************
       Table: es3
Create Table: CREATE TABLE `es3` (
  `id` int(11) NOT NULL,
  `fname` varchar(30) DEFAULT NULL,
  `lname` varchar(30) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
1 row in set (0.01 sec)


mysql> ALTER TABLE es3 ENGINE = MyISAM;
Query OK, 0 rows affected (0.01 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> ALTER TABLE es EXCHANGE PARTITION p3sp0 WITH TABLE es3;
ERROR 1497 (HY000): The mix of handlers in the partitions is not allowed in this version of MySQL

MySQL 5.7 用非分区表交换分区

用表交换分区和子分区
在MySQL 5.7中,可以使用ALTER table pt exchange partition p with table nt来交换一个表分区或子分区,其中pt是已分区的表,p是要与 未分区的表nt交换的pt的分区或子分区,前提是以下条件成立:
1.表nt本身没有分区。

2.表nt不是一个临时表。

3.表pt和表nt的结构在其他方面是相同的。

4.表nt不包含外键引用,其他表也没有任何外键引用nt。

5.nt中没有行位于p的分区定义边界之外。如果使用了WITHOUT VALIDATION选项,则不适用此条件。在MySQL 5.7.5中增加了[{WITH|WITHOUT} VALIDATION]选项。

除了ALTER TABLE语句通常需要的ALTER、INSERT和CREATE权限外,还必须有DROP权限才能执行ALTER TABLE … EXCHANGE PARTITION。

您还应该注意ALTER TABLE … EXCHANGE PARTITION有以下影响:
.执行ALTER TABLE…EXCHANGE PARTITION不调用分区表或要交换的表上的任何触发器。

.交换表中的AUTO_INCREMENT列将被重置。

.IGNORE关键字对IGNORE关键字对ALTER TABLE…不起作用。交换分区。不起作用。

ALTER TABLE …EXCHANGE PARTITION语句的语法这里给出了,其中pt是已分区表,p是要交换的分区或子分区,nt是要交换的非分区表:
ALTER TABLE pt
EXCHANGE PARTITION p
WITH TABLE nt;

可选地,您可以添加 WITH VALIDATION 或 WITHOUT VALIDATION 子句。当指定 WITHOUT VALIDATION 时,在将分区与非分区表交换时,ALTER TABLE…EXCHANGE PARTITION 操作不会逐行验证,允许数据库管理员承担确保行在分区定义边界内的责任。WITH VALIDATION 是默认行为,无 需显式指定。[{WITH|WITHOUT} VALIDATION] 选项在 MySQL 5.7.5 中添加。

在单个 ALTER TABLE EXCHANGE PARTITION 语句中,只能将一个分区或子分区与一个非分区表进行交换。若要交换多个分区或子分区,请使用多 个 ALTER TABLE EXCHANGE PARTITION 语句。EXCHANGE PARTITION 不能与其他 ALTER TABLE 选项结合使用。分区表所使用的分区和(如适用) 子分区类型可以是 MySQL 5.7 支持的任何类型。

用非分区表交换分区
假设已经创建了一个分区表e,并使用以下SQL语句填充:

mysql> CREATE TABLE e (
    -> id INT NOT NULL,
    -> fname VARCHAR(30),
    -> lname VARCHAR(30)
    -> )
    -> PARTITION BY RANGE (id) (
    -> PARTITION p0 VALUES LESS THAN (50),
    -> PARTITION p1 VALUES LESS THAN (100),
    -> PARTITION p2 VALUES LESS THAN (150),
    -> PARTITION p3 VALUES LESS THAN (MAXVALUE)
    -> );
Query OK, 0 rows affected (0.03 sec)

mysql> INSERT INTO e VALUES
    -> (1669, "Jim", "Smith"),
    -> (337, "Mary", "Jones"),
    -> (16, "Frank", "White"),
    -> (2005, "Linda", "Black");
Query OK, 4 rows affected (0.00 sec)
Records: 4  Duplicates: 0  Warnings: 0

现在我们创建了e的一个未分区副本e2。这可以使用mysql客户端来完成,如下所示:

mysql> CREATE TABLE e2 LIKE e;
Query OK, 0 rows affected (0.02 sec)

mysql> ALTER TABLE e2 REMOVE PARTITIONING;
Query OK, 0 rows affected (0.02 sec)
Records: 0  Duplicates: 0  Warnings: 0

你可以通过查询INFORMATION_SCHEMA.PARTITIONS表来查看表e中哪些分区包含了哪些行,如下所示:

mysql> SELECT PARTITION_NAME, TABLE_ROWS  FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_NAME = 'e';
+----------------+------------+
| PARTITION_NAME | TABLE_ROWS |
+----------------+------------+
| p0             |          1 |
| p1             |          0 |
| p2             |          0 |
| p3             |          3 |
+----------------+------------+
4 rows in set (0.01 sec)

对于分区后的InnoDB表,在INFORMATION_SCHEMA.PARTITIONS的TABLE_ROWS列中给出行数。只是SQL优化中使用的一个估计值,并不总是精确的。

要将表e中的分区p0与表e2交换,可以使用ALTER TABLE语句:

mysql> ALTER TABLE e EXCHANGE PARTITION p0 WITH TABLE e2;
Query OK, 0 rows affected (0.01 sec)

更准确地说,这条语句会导致在分区中找到的任何行与在表中找到的行进行交换。你可以通过查询INFORMATION_SCHEMA.PARTITIONS来观察这是 如何发生的。和之前一样,发现找表分区p0不再存在行数据:

mysql> SELECT PARTITION_NAME, TABLE_ROWS  FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_NAME = 'e';
+----------------+------------+
| PARTITION_NAME | TABLE_ROWS |
+----------------+------------+
| p0             |          0 |
| p1             |          0 |
| p2             |          0 |
| p3             |          3 |
+----------------+------------+
4 rows in set (0.00 sec)

如果你查询表e2,你可以看到“缺失”行现在可以在那里找到:

mysql> SELECT * FROM e2;
+----+-------+-------+
| id | fname | lname |
+----+-------+-------+
| 16 | Frank | White |
+----+-------+-------+
1 row in set (0.00 sec)

与分区交换的表不一定是空的。为了证明这一点,我们首先向表e中插入一个新行,通过选择一个id值小于50的列来确保该行存储在分区p0中, 然后通过查询分区表来验证这一点:

mysql> INSERT INTO e VALUES (41, "Michael", "Green");
Query OK, 1 row affected (0.00 sec)

mysql> SELECT PARTITION_NAME, TABLE_ROWS  FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_NAME = 'e';
+----------------+------------+
| PARTITION_NAME | TABLE_ROWS |
+----------------+------------+
| p0             |          1 |
| p1             |          0 |
| p2             |          0 |
| p3             |          3 |
+----------------+------------+
4 rows in set (0.01 sec)

现在我们再次使用与之前相同的ALTER table语句将分区p0与表e2交换:

mysql> ALTER TABLE e EXCHANGE PARTITION p0 WITH TABLE e2;
Query OK, 0 rows affected (0.01 sec)

下面查询的输出显示,在发出ALTER TABLE语句之前,存储在分区p0中的表行和存储在表e2中的表行现在交换了位置:

mysql> SELECT * FROM e;
+------+-------+-------+
| id   | fname | lname |
+------+-------+-------+
|   16 | Frank | White |
| 1669 | Jim   | Smith |
|  337 | Mary  | Jones |
| 2005 | Linda | Black |
+------+-------+-------+
4 rows in set (0.01 sec)

mysql> SELECT PARTITION_NAME, TABLE_ROWS  FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_NAME = 'e';
+----------------+------------+
| PARTITION_NAME | TABLE_ROWS |
+----------------+------------+
| p0             |          1 |
| p1             |          0 |
| p2             |          0 |
| p3             |          3 |
+----------------+------------+
4 rows in set (0.00 sec)

mysql> SELECT * FROM e2;
+----+---------+-------+
| id | fname   | lname |
+----+---------+-------+
| 41 | Michael | Green |
+----+---------+-------+
1 row in set (0.00 sec)

不匹配的行
您应该记住,在发出ALTER TABLE … EXCHANGE PARTITION语句之前,在未分区的表中找到的任何行必须满足将它们存储在目标分区中的条件; 否则,语句执行失败。要了解这是如何发生的,首先在e2中插入一条超出表e的p0分区定义边界的行。例如,插入一条id列值过大的行;然后, 尝试再次用分区交换表:

mysql> INSERT INTO e2 VALUES (51, "Ellen", "McDonald");
Query OK, 1 row affected (0.00 sec)

mysql> ALTER TABLE e EXCHANGE PARTITION p0 WITH TABLE e2;
ERROR 1737 (HY000): Found a row that does not match the partition

只有WITHOUT VALIDATION选项允许此操作成功:

mysql> ALTER TABLE e EXCHANGE PARTITION p0 WITH TABLE e2 WITHOUT VALIDATION;
Query OK, 0 rows affected (0.01 sec)

当一个分区与包含与分区定义不匹配的行的表交换时,数据库管理员的责任是修复不匹配的行,这可以使用REPAIR TABLE或AALTER TABLE … REPAIR PARTITION.

mysql> select * from e partition(p0);
+----+---------+----------+
| id | fname   | lname    |
+----+---------+----------+
| 41 | Michael | Green    |
| 51 | Ellen   | McDonald |
+----+---------+----------+
2 rows in set (0.00 sec)

mysql> repair table e;
+--------+--------+----------+------------------------+
| Table  | Op     | Msg_type | Msg_text               |
+--------+--------+----------+------------------------+
| test.e | repair | warning  | Moved 1 misplaced rows |
| test.e | repair | status   | OK                     |
+--------+--------+----------+------------------------+
2 rows in set (0.01 sec)

mysql> select * from e partition(p0);
+----+---------+-------+
| id | fname   | lname |
+----+---------+-------+
| 41 | Michael | Green |
+----+---------+-------+
1 row in set (0.00 sec)

不逐行验证的交换分区
为了避免在与有许多行的表交换分区时进行耗时的验证,可以通过向ALTER TABLE … EXCHANGE PARTITION语句追加WITHOUT VALIDATION来跳过 逐行验证步骤。

下面的例子比较了在使用和不使用验证的情况下,用非分区表交换分区时执行时间的差异。分区表(表e)包含两个分区,每个分区有100万行。 删除表e中p0中的行,将p0与一个100万行的未分区表交换。使验证操作耗时0.74秒。相比之下,无验证操作需要0.01秒。

# Create a partitioned table with 1 million rows in each partition
CREATE TABLE e (
id INT NOT NULL,
fname VARCHAR(30),
lname VARCHAR(30)
)
PARTITION BY RANGE (id) (
PARTITION p0 VALUES LESS THAN (1000001),
PARTITION p1 VALUES LESS THAN (2000001),
);
SELECT COUNT(*) FROM e;
| COUNT(*) |
+----------+
| 2000000  |
+----------+
1 row in set (0.27 sec)
# View the rows in each partition
SELECT PARTITION_NAME, TABLE_ROWS FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_NAME = 'e';
+----------------+-------------+
| PARTITION_NAME |  TABLE_ROWS |
+----------------+-------------+
| p0             |     1000000 |
| p1             |     1000000 |
+----------------+-------------+
2 rows in set (0.00 sec)
# Create a nonpartitioned table of the same structure and populate it with 1 million rows
CREATE TABLE e2 (
id INT NOT NULL,
fname VARCHAR(30),
lname VARCHAR(30)
);
mysql> SELECT COUNT(*) FROM e2;
+----------+
| COUNT(*) |
+----------+
|  1000000 |
+----------+
1 row in set (0.24 sec)
# Create another nonpartitioned table of the same structure and populate it with 1 million rows
CREATE TABLE e3 (
id INT NOT NULL,
fname VARCHAR(30),
lname VARCHAR(30)
);
mysql> SELECT COUNT(*) FROM e3;
+----------+
| COUNT(*) |
+----------+
|  1000000 |
+----------+
1 row in set (0.25 sec)
# Drop the rows from p0 of table e

mysql> DELETE FROM e WHERE id < 1000001;
Query OK, 1000000 rows affected (5.55 sec)
# Confirm that there are no rows in partition p0
mysql> SELECT PARTITION_NAME, TABLE_ROWS FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_NAME = 'e';
+----------------+------------+
| PARTITION_NAME | TABLE_ROWS |
+----------------+------------+
| p0             |          0 |
| p1             |    1000000 |
+----------------+------------+
2 rows in set (0.00 sec)
# Exchange partition p0 of table e with the table e2 'WITH VALIDATION'
mysql> ALTER TABLE e EXCHANGE PARTITION p0 WITH TABLE e2 WITH VALIDATION;
Query OK, 0 rows affected (0.74 sec)
# Confirm that the partition was exchanged with table e2
mysql> SELECT PARTITION_NAME, TABLE_ROWS FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_NAME = 'e';
+----------------+------------+
| PARTITION_NAME | TABLE_ROWS |
+----------------+------------+
| p0             |    1000000 |
| p1             |    1000000 |
+----------------+------------+
2 rows in set (0.00 sec)
# Once again, drop the rows from p0 of table e
mysql> DELETE FROM e WHERE id < 1000001;
Query OK, 1000000 rows affected (5.55 sec)
# Confirm that there are no rows in partition p0
mysql> SELECT PARTITION_NAME, TABLE_ROWS FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_NAME = 'e';
+----------------+------------+
| PARTITION_NAME | TABLE_ROWS |
+----------------+------------+
| p0             |          0 |
| p1             |    1000000 |
+----------------+------------+
2 rows in set (0.00 sec)
# Exchange partition p0 of table e with the table e3 'WITHOUT VALIDATION'
mysql> ALTER TABLE e EXCHANGE PARTITION p0 WITH TABLE e3 WITHOUT VALIDATION;
Query OK, 0 rows affected (0.01 sec)
# Confirm that the partition was exchanged with table e3
mysql> SELECT PARTITION_NAME, TABLE_ROWS FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_NAME = 'e';
+----------------+------------+
| PARTITION_NAME | TABLE_ROWS |
+----------------+------------+
| p0             |    1000000 |
| p1             |    1000000 |
+----------------+------------+
2 rows in set (0.00 sec)

如果一个分区与包含与分区定义不匹配的行的表交换,则数据库管理员有责任修复不匹配的行,这可以使用REPAIR TABLE或ALTER TABLE … REPAIR PARTITION修复分区。

MySQL 5.7 管理哈希和键分区

管理哈希和键分区
按哈希或按键分区的表在更改分区设置方面非常相似,两者与按范围或列表分区的表在许多方面有所不同。因此,本节讨论的是对按哈希或按键 分区表的修改。

您不能像从按范围或列表分区的表中删除分区那样,从哈希或按键分区的表中删除分区。但是,你可以使用ALTER TABLE … COALESCE PARTITION语句来合并哈希分区或键分区。假设你有一个包含客户端数据的表,分为12个分区。clients表定义如下:

mysql> CREATE TABLE clients (
    -> id INT,
    -> fname VARCHAR(30),
    -> lname VARCHAR(30),
    -> signed DATE
    -> )
    -> PARTITION BY HASH( MONTH(signed) )
    -> PARTITIONS 12;
Query OK, 0 rows affected (0.07 sec)

要将分区数量从12个减少到8个,请执行以下ALTER TABLE命令:

mysql> ALTER TABLE clients COALESCE PARTITION 4;
Query OK, 0 rows affected (0.09 sec)
Records: 0  Duplicates: 0  Warnings: 0

COALESCE同样适用于按HASH、KEY、LINEAR HASH或LINEAR KEY分区的表。下面的例子与前一个类似,不同之处在于这张表是按线性键分区的:

mysql> CREATE TABLE clients_lk (
    -> id INT,
    -> fname VARCHAR(30),
    -> lname VARCHAR(30),
    -> signed DATE
    -> )
    -> PARTITION BY LINEAR KEY(signed)
    -> PARTITIONS 12;
Query OK, 0 rows affected (0.03 sec)

mysql> ALTER TABLE clients_lk COALESCE PARTITION 4;
Query OK, 0 rows affected (0.05 sec)
Records: 0  Duplicates: 0  Warnings: 0

COALESCE PARTITION后面的数字是要合并到剩余分区中的分区数量——换句话说,它是要从表中删除的分区数量。

如果您试图删除比表所拥有的更多的分区,则结果是如下所示的错误:

mysql> ALTER TABLE clients COALESCE PARTITION 18;
ERROR 1508 (HY000): Cannot remove all partitions, use DROP TABLE instead

将客户端表的分区数量从12增加到18。使用ALTER TABLE … ADD PARTITION来添加分区如下所示:

mysql> ALTER TABLE clients ADD PARTITION PARTITIONS 6;
Query OK, 0 rows affected (0.17 sec)
Records: 0  Duplicates: 0  Warnings: 0

MySQL 5.7 范围和列表分区的管理

范围和列表分区的管理
范围分区和列表分区的添加和删除以类似的方式处理,因此我们将在本节中讨论这两种分区的管理。

从按范围或按列表分区的表中删除分区,可以使用带有DROP PARTITION选项的ALTER TABLE语句来完成。假设你已经创建了一个按范围分区的表 ,然后使用下面的CREATE TABLE和INSERT语句填充了10条记录:

mysql> CREATE TABLE tr (id INT, name VARCHAR(50), purchased DATE)
    -> PARTITION BY RANGE( YEAR(purchased) ) (
    -> PARTITION p0 VALUES LESS THAN (1990),
    -> PARTITION p1 VALUES LESS THAN (1995),
    -> PARTITION p2 VALUES LESS THAN (2000),
    -> PARTITION p3 VALUES LESS THAN (2005),
    -> PARTITION p4 VALUES LESS THAN (2010),
    -> PARTITION p5 VALUES LESS THAN (2015)
    -> );
Query OK, 0 rows affected (0.03 sec)



mysql> INSERT INTO tr VALUES
    -> (1, 'desk organiser', '2003-10-15'),
    -> (2, 'alarm clock', '1997-11-05'),
    -> (3, 'chair', '2009-03-10'),
    -> (4, 'bookcase', '1989-01-10'),
    -> (5, 'exercise bike', '2014-05-09'),
    -> (6, 'sofa', '1987-06-05'),
    -> (7, 'espresso maker', '2011-11-22'),
    -> (8, 'aquarium', '1992-08-04'),
    -> (9, 'study desk', '2006-09-16'),
    -> (10, 'lava lamp', '1998-12-25');
Query OK, 10 rows affected (0.00 sec)
Records: 10  Duplicates: 0  Warnings: 0

您可以看到哪些项应该插入到分区p2中,如下所示:

mysql> SELECT * FROM tr WHERE purchased BETWEEN '1995-01-01' AND '1999-12-31';
+------+-------------+------------+
| id   | name        | purchased  |
+------+-------------+------------+
|    2 | alarm clock | 1997-11-05 |
|   10 | lava lamp   | 1998-12-25 |
+------+-------------+------------+
2 rows in set (0.00 sec)

您也可以使用分区选择来获得这些信息,如下所示:

mysql> SELECT * FROM tr PARTITION (p2);
+------+-------------+------------+
| id   | name        | purchased  |
+------+-------------+------------+
|    2 | alarm clock | 1997-11-05 |
|   10 | lava lamp   | 1998-12-25 |
+------+-------------+------------+
2 rows in set (0.00 sec)

删除名称为“p2”的分区。

mysql> ALTER TABLE tr DROP PARTITION p2;
Query OK, 0 rows affected (0.07 sec)
Records: 0  Duplicates: 0  Warnings: 0

有一点非常重要,要记住,当删除一个分区时,也会删除存储在该分区中的所有数据。通过重新运行上一个SELECT查询,可以看到这一点:

mysql> SELECT * FROM tr WHERE purchased BETWEEN '1995-01-01' AND '1999-12-31';
Empty set (0.00 sec)

因此,在执行ALTER TABLE …DROP PARTITION之前,用户必须对一张表具有DROP权限。

如果希望从所有分区中删除所有数据,同时保留表定义及其分区方案,请使用TRUNCATE TABLE语句。

如果您想在不丢失数据的情况下更改表的分区,请使用ALTER TABLE …REORGANIZE PARTITION。

如果你现在执行SHOW CREATE TABLE语句,你可以看到表的分区组成是如何被改变的:

mysql> SHOW CREATE TABLE tr\G
*************************** 1. row ***************************
       Table: tr
Create Table: CREATE TABLE `tr` (
  `id` int(11) DEFAULT NULL,
  `name` varchar(50) DEFAULT NULL,
  `purchased` date DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
/*!50100 PARTITION BY RANGE ( YEAR(purchased))
(PARTITION p0 VALUES LESS THAN (1990) ENGINE = InnoDB,
 PARTITION p1 VALUES LESS THAN (1995) ENGINE = InnoDB,
 PARTITION p3 VALUES LESS THAN (2005) ENGINE = InnoDB,
 PARTITION p4 VALUES LESS THAN (2010) ENGINE = InnoDB,
 PARTITION p5 VALUES LESS THAN (2015) ENGINE = InnoDB) */
1 row in set (0.00 sec)

当你将购买的列值在‘1995-01-01’到‘2004-12-31’之间的新行插入到更改后的表中时,这些行将存储在分区p3中。你可以像下面这样验证:

mysql> INSERT INTO tr VALUES (11, 'pencil holder', '1995-07-12');
Query OK, 1 row affected (0.00 sec)

mysql> SELECT * FROM tr WHERE purchased BETWEEN '1995-01-01' AND '2004-12-31';
+------+----------------+------------+
| id   | name           | purchased  |
+------+----------------+------------+
|    1 | desk organiser | 2003-10-15 |
|   11 | pencil holder  | 1995-07-12 |
+------+----------------+------------+
2 rows in set (0.00 sec)

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

mysql> SELECT * FROM tr WHERE purchased BETWEEN '1995-01-01' AND '2004-12-31';
Empty set (0.00 sec)

由于ALTER TABLE … DROP PARTITION操作从表中删除的行数不像同级的DELETE查询那样由服务器报告。

删除列表分区使用与删除范围分区完全相同的ALTER TABLE … DROP PARTITION语法。然而,这对之后使用表的影响有一个重要的区别:你不能 再向表中插入任何包含定义删除分区的值列表中的值的行。

要向已分区的表添加新的范围分区或列表分区,请使用ALTER TABLE … ADD PARTITION语句。对于按范围分区的表,这可以用来在现有分区列 表的末尾添加一个新的范围。假设你有一个包含组织成员数据的分区表,定义如下:

mysql> CREATE TABLE members (
    -> id INT,
    -> fname VARCHAR(25),
    -> lname VARCHAR(25),
    -> dob DATE
    -> )
    -> PARTITION BY RANGE( YEAR(dob) ) (
    -> PARTITION p0 VALUES LESS THAN (1980),
    -> PARTITION p1 VALUES LESS THAN (1990),
    -> PARTITION p2 VALUES LESS THAN (2000)
    -> );
Query OK, 0 rows affected (0.01 sec)

进一步假设成员的最低年龄为16岁。随着日历临近2015年年底,你意识到你很快就会接纳2000年(及之后)出生的成员。你可以修改成员表以容 纳2000年到2010年出生的新成员,如下所示:

mysql> ALTER TABLE members ADD PARTITION (PARTITION p3 VALUES LESS THAN (2010));
Query OK, 0 rows affected (0.02 sec)
Records: 0  Duplicates: 0  Warnings: 0

对于按范围分区的表,您可以使用ADD PARTITION将新分区仅添加到分区列表的高端。试图在现有分区之间或现有分区之前以这种方式添加新分 区会导致如下错误:

mysql> ALTER TABLE members ADD PARTITION (PARTITION n VALUES LESS THAN (1970));
ERROR 1493 (HY000): VALUES LESS THAN value must be strictly increasing for each partition

您可以通过将第一个分区重新组织为两个新的分区来解决这个问题,这样可以将它们之间的范围分开:

mysql> ALTER TABLE members
    -> REORGANIZE PARTITION p0 INTO (
    -> PARTITION n0 VALUES LESS THAN (1970),
    -> PARTITION n1 VALUES LESS THAN (1980)
    -> );
Query OK, 0 rows affected (0.02 sec)
Records: 0  Duplicates: 0  Warnings: 0

使用SHOW CREATE TABLE可以看到ALTER TABLE语句达到了预期效果:

mysql> SHOW CREATE TABLE members\G
*************************** 1. row ***************************
       Table: members
Create Table: CREATE TABLE `members` (
  `id` int(11) DEFAULT NULL,
  `fname` varchar(25) DEFAULT NULL,
  `lname` varchar(25) DEFAULT NULL,
  `dob` date DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
/*!50100 PARTITION BY RANGE ( YEAR(dob))
(PARTITION n0 VALUES LESS THAN (1970) ENGINE = InnoDB,
 PARTITION n1 VALUES LESS THAN (1980) ENGINE = InnoDB,
 PARTITION p1 VALUES LESS THAN (1990) ENGINE = InnoDB,
 PARTITION p2 VALUES LESS THAN (2000) ENGINE = InnoDB,
 PARTITION p3 VALUES LESS THAN (2010) ENGINE = InnoDB) */
1 row in set (0.00 sec)

你也可以使用ALTER TABLE … ADD PARTITION向按列表分区的表中添加新分区。假设表tt是用下面的CREATE table语句定义的:

mysql> 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)
    -> );
Query OK, 0 rows affected (0.01 sec)

您可以添加一个新的分区,其中存储数据列值为7,14和21的行,如下所示:

mysql> ALTER TABLE tt ADD PARTITION (PARTITION p2 VALUES IN (7, 14, 21));
Query OK, 0 rows affected (0.00 sec)
Records: 0  Duplicates: 0  Warnings: 0

请记住,不能添加包含现有分区的值列表中已经包含的任何值的新LIST分区。如果你尝试这样做,将会产生一个错误:

mysql> ALTER TABLE tt ADD PARTITION (PARTITION np VALUES IN (4, 8, 12));
ERROR 1495 (HY000): Multiple definition of same constant in list partitioning

因为数据列值为12的任何行都已经分配给了分区p1,所以不能在表tt上创建一个包含12的新分区。为此,可以删除p1,添加np,再创建一个定义 有所修改的p1。但是,如前所述,这将导致存储在p1中的所有数据丢失,而通常情况下这并不是我们真正想要的。另一种解决方案可能是在新的 分区下复制表,然后使用CREATE TABLE … SELECT …,然后删除旧表并重命名新表,但在处理大量数据时,这可能非常耗时。在要求高可用 性的情况下,这可能也是不可行的。

您可以在单个ALTER TABLE中添加多个分区。ADD PARTITION语句如下所示:

mysql> CREATE TABLE employees (
    -> id INT NOT NULL,
    -> fname VARCHAR(50) NOT NULL,
    -> lname VARCHAR(50) NOT NULL,
    -> hired DATE NOT NULL
    -> )
    -> PARTITION BY RANGE( YEAR(hired) ) (
    -> PARTITION p1 VALUES LESS THAN (1991),
    -> PARTITION p2 VALUES LESS THAN (1996),
    -> PARTITION p3 VALUES LESS THAN (2001),
    -> PARTITION p4 VALUES LESS THAN (2005)
    -> );
Query OK, 0 rows affected (0.02 sec)

mysql> ALTER TABLE employees ADD PARTITION (
    -> PARTITION p5 VALUES LESS THAN (2010),
    -> PARTITION p6 VALUES LESS THAN MAXVALUE
    -> );
Query OK, 0 rows affected (0.08 sec)
Records: 0  Duplicates: 0  Warnings: 0

幸运的是,MySQL的分区实现提供了重新定义分区而不会丢失数据的方法。我们首先看几个涉及范围分区的简单例子。回想一下成员表的定义, 如下所示:

mysql> SHOW CREATE TABLE members\G
*************************** 1. row ***************************
       Table: members
Create Table: CREATE TABLE `members` (
  `id` int(11) DEFAULT NULL,
  `fname` varchar(25) DEFAULT NULL,
  `lname` varchar(25) DEFAULT NULL,
  `dob` date DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
/*!50100 PARTITION BY RANGE ( YEAR(dob))
(PARTITION n0 VALUES LESS THAN (1970) ENGINE = InnoDB,
 PARTITION n1 VALUES LESS THAN (1980) ENGINE = InnoDB,
 PARTITION p1 VALUES LESS THAN (1990) ENGINE = InnoDB,
 PARTITION p2 VALUES LESS THAN (2000) ENGINE = InnoDB,
 PARTITION p3 VALUES LESS THAN (2010) ENGINE = InnoDB) */
1 row in set (0.00 sec)

假设您想将表示1960年以前出生的成员的所有行移动到一个单独的分区中。我们已经看到,这不能使用ALTER TABLE … ADD PARTITION添加分 区。不过,用户可以使用另一个与分区相关的扩展ALTER TABLE来实现这一点:

mysql> ALTER TABLE members REORGANIZE PARTITION n0 INTO (
    -> PARTITION s0 VALUES LESS THAN (1960),
    -> PARTITION s1 VALUES LESS THAN (1970)
    -> );
Query OK, 0 rows affected (0.02 sec)
Records: 0  Duplicates: 0  Warnings: 0

实际上,该命令将分区p0分成两个新的分区s0和s1。它还根据PARTITION …VALUES …子句所体现的规则,将存储在p0中的数据移动到新的分 区中。,使得s0只包含年份(dob)小于1960的那些记录,s1包含年份(dob)大于或等于1960但小于1970的那些行。

REORGANIZE PARTITION子句也可以用于合并相邻的分区。你可以将前面的语句对members表的影响颠倒过来,如下所示:

mysql> ALTER TABLE members REORGANIZE PARTITION s0,s1 INTO (
    -> PARTITION p0 VALUES LESS THAN (1970)
    -> );
Query OK, 0 rows affected (0.02 sec)
Records: 0  Duplicates: 0  Warnings: 0

使用REORGANIZE PARTITION拆分或合并分区时不会丢失数据。在执行上述语句时,MySQL将存储在分区50和s1中的所有记录移动到分区p0中。

REORGANIZE PARTITION的通用语法如下所示:

ALTER TABLE tbl_name REORGANIZE PARTITION partition_list INTO (partition_definitions);

这里,tbl_name是分区表的名称,而partition_list是一个以逗号分隔的列表,其中包含一个或多个要更改的现有分区的名称。 partition_definitions是一个逗号分隔的新分区定义列表,它遵循与CREATE TABLE中使用的partition_definitions列表相同的规则。在使用 REORGANIZE PARTITION时,您并不局限于将几个分区合并为一个分区,或者将一个分区拆分为多个分区。例如,您可以将members表的所有四个 分区重组为两个,如下所示:

mysql> ALTER TABLE members REORGANIZE PARTITION p0,n1,p1,p2 INTO (
    -> PARTITION m0 VALUES LESS THAN (1980),
    -> PARTITION m1 VALUES LESS THAN (2000)
    -> );
Query OK, 0 rows affected (0.09 sec)
Records: 0  Duplicates: 0  Warnings: 0

您还可以对通过LIST进行分区的表使用REORGANIZE PARTITION。让我们回到向列表分区tt表添加新分区并失败的问题,因为新分区的值已经存在 于一个现有分区的值列表中。我们可以通过添加一个只包含非冲突值的分区来处理这个问题,然后重新组织新分区和现有分区,以便存储在现有 分区中的值现在移动到新分区中:

mysql> ALTER TABLE tt ADD PARTITION (PARTITION np VALUES IN (4, 8));
Query OK, 0 rows affected (0.02 sec)
Records: 0  Duplicates: 0  Warnings: 0


mysql> ALTER TABLE tt REORGANIZE PARTITION p2,np INTO (
    -> PARTITION p2 VALUES IN (7, 14),
    -> PARTITION np VALUES in (4, 8, 21)
    -> );
Query OK, 0 rows affected (0.02 sec)
Records: 0  Duplicates: 0  Warnings: 0

以下是使用ALTER TABLE…REORGANIZE PARTITION对按范围或列表分区的表进行重新分区时需要注意的一些关键点:
.用于确定新分区方案的PARTITION选项遵循与CREATE TABLE语句相同的规则。

一个新的范围分区方案不能有任何重叠的范围;新的列表分区方案不能有任何重叠的值集。

.partition_definitions列表中的分区组合总体上应该与partition_list中命名的组合分区的范围或值集相同。

例如,分区p1和p2一起覆盖了成员表中的1980年到1999年。这两个分区的任何重组都应该覆盖相同的年份范围。

.对于按范围分区的表,可以只重组相邻的分区;您不能跳过范围分区。

例如,不能使用以ALTER TABLE members REORGANIZE PARTITION p0,p2 INTO …开头的语句来重组members表。因为p0包含1970年之前的年份, 而p2包含1990年到1999年的年份,所以它们不是相邻的分区。(在这种情况下不能跳过分区p1。)

.不能使用REORGANIZE PARTITION分区来更改表使用的分区类型;例如,您不能将范围分区更改为散列分区或相反。也不能使用此语句更改分区 表达式或列。要在不删除和重新创建表的情况下完成上述任务,可以使用ALTER TABLE … PARTITION BY …,如下所示:

mysql> ALTER TABLE members
    -> PARTITION BY HASH( YEAR(dob) )
    -> PARTITIONS 8;
Query OK, 0 rows affected (0.04 sec)
Records: 0  Duplicates: 0  Warnings: 0