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