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

MySQL5.7 分区如何处理NULL

MySQL分区如何处理NULL
MySQL中的分区并没有禁止NULL作为分区表达式的值,无论是列值还是用户提供的表达式的值。尽管允许使用NULL作为表达式的值,但要记住 NULL不是数字,这一点很重要。MySQL的分区实现将NULL视为小于任何非空值,就像ORDER BY那样。

这意味着不同类型的分区对NULL的处理方式不同,如果你没有做好准备,可能会产生意想不到的行为。在这种情况下,本节将讨论MySQL分区类 型如何处理NULL值,并分别提供示例。

范围分区处理NULL。
如果你将一行插入到一个按范围分区的表中,以便用于确定分区的列值为NULL,那么这一行将插入到最低的分区中。考虑数据库test中的这两张表,创建方法如下:

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

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

在INFORMATION_SCHEMA数据库中,用户可以使用以下查询来查看这两个CREATE TABLE语句创建的分区:

mysql> SELECT TABLE_NAME, PARTITION_NAME, TABLE_ROWS, AVG_ROW_LENGTH, DATA_LENGTH  FROM INFORMATION_SCHEMA.PARTITIONS WHERE  TABLE_SCHEMA = 'test' AND TABLE_NAME LIKE 't_';
+------------+----------------+------------+----------------+-------------+
| TABLE_NAME | PARTITION_NAME | TABLE_ROWS | AVG_ROW_LENGTH | DATA_LENGTH |
+------------+----------------+------------+----------------+-------------+
| t1         | p0             |          0 |              0 |       16384 |
| t1         | p1             |          0 |              0 |       16384 |
| t1         | p2             |          0 |              0 |       16384 |
| t2         | p0             |          0 |              0 |       16384 |
| t2         | p1             |          0 |              0 |       16384 |
| t2         | p2             |          0 |              0 |       16384 |
| t2         | p3             |          0 |              0 |       16384 |
+------------+----------------+------------+----------------+-------------+
7 rows in set (0.01 sec)

现在让我们在每个表中插入一行包含NULL作为分区键的列,并使用一对SELECT语句验证这些行是否被插入:

mysql> INSERT INTO t1 VALUES (NULL, 'mothra');
Query OK, 1 row affected (0.00 sec)

mysql> INSERT INTO t2 VALUES (NULL, 'mothra');
Query OK, 1 row affected (0.00 sec)

mysql> SELECT * FROM t1;
+------+--------+
| c1   | c2     |
+------+--------+
| NULL | mothra |
+------+--------+
1 row in set (0.00 sec)

mysql> SELECT * FROM t2;
+------+--------+
| c1   | c2     |
+------+--------+
| NULL | mothra |
+------+--------+
1 row in set (0.00 sec)

通过再次对INFORMATION_SCHEMA.PARTITIONS表运行前面的查询,您可以看到哪些分区用于存储插入的行:

mysql> SELECT TABLE_NAME, PARTITION_NAME, TABLE_ROWS, AVG_ROW_LENGTH, DATA_LENGTH  FROM INFORMATION_SCHEMA.PARTITIONS WHERE  TABLE_SCHEMA = 'test' AND TABLE_NAME LIKE 't_';
+------------+----------------+------------+----------------+-------------+
| TABLE_NAME | PARTITION_NAME | TABLE_ROWS | AVG_ROW_LENGTH | DATA_LENGTH |
+------------+----------------+------------+----------------+-------------+
| t1         | p0             |          1 |          16384 |       16384 |
| t1         | p1             |          0 |              0 |       16384 |
| t1         | p2             |          0 |              0 |       16384 |
| t2         | p0             |          1 |          16384 |       16384 |
| t2         | p1             |          0 |              0 |       16384 |
| t2         | p2             |          0 |              0 |       16384 |
| t2         | p3             |          0 |              0 |       16384 |
+------------+----------------+------------+----------------+-------------+
7 rows in set (0.00 sec)

用户还可以删除这些分区,然后重新运行SELECT语句,证明这些行存储在每个表的最低分区中:

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

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

mysql> SELECT * FROM t1;
Empty set (0.00 sec)

mysql> SELECT * FROM t2;
Empty set (0.00 sec)

对于使用SQL函数的分区表达式,NULL也是这样处理的。假设我们使用CREATE TABLE语句定义一个表,如下所示:

mysql> CREATE TABLE tndate (
    -> id INT,
    -> dt DATE
    -> )
    -> PARTITION BY RANGE( YEAR(dt) ) (
    -> PARTITION p0 VALUES LESS THAN (1990),
    -> PARTITION p1 VALUES LESS THAN (2000),
    -> PARTITION p2 VALUES LESS THAN MAXVALUE
    -> );
Query OK, 0 rows affected (0.01 sec)

与其他MySQL函数一样,YEAR(NULL)返回NULL。dt列值为NULL的行被视为分区表达式的值小于任何其他值,因此被插入到分区p0中。

列表分区处理NULL。
按列表分区的表,当且仅当其中一个分区是用包含NULL的值列表定义的时,才允许NULL值。与之相反的是,按LIST分区的表如果没有在值列表中 显式使用NULL,则会拒绝在分区表达式中使用NULL值,如下面的例子所示:

mysql> CREATE TABLE ts1 (
    -> c1 INT,
    -> c2 VARCHAR(20)
    -> )
    -> PARTITION BY LIST(c1) (
    -> PARTITION p0 VALUES IN (0, 3, 6),
    -> PARTITION p1 VALUES IN (1, 4, 7),
    -> PARTITION p2 VALUES IN (2, 5, 8)
    -> );
Query OK, 0 rows affected (0.01 sec)

mysql> INSERT INTO ts1 VALUES (9, 'mothra');
ERROR 1526 (HY000): Table has no partition for value 9
mysql> INSERT INTO ts1 VALUES (NULL, 'mothra');
ERROR 1526 (HY000): Table has no partition for value NULL

只有c1值在0到8之间(包括0和8)的行才能插入到ts1中。NULL超出了这个范围,就像数字9一样。我们可以创建包含NULL的值列表的表ts2和ts3 ,如下所示:

mysql> CREATE TABLE ts2 (
    -> c1 INT,
    -> c2 VARCHAR(20)
    -> )
    -> PARTITION BY LIST(c1) (
    -> PARTITION p0 VALUES IN (0, 3, 6),
    -> PARTITION p1 VALUES IN (1, 4, 7),
    -> PARTITION p2 VALUES IN (2, 5, 8),
    -> PARTITION p3 VALUES IN (NULL)
    -> );
Query OK, 0 rows affected (0.04 sec)

mysql> CREATE TABLE ts3 (
    -> c1 INT,
    -> c2 VARCHAR(20)
    -> )
    -> PARTITION BY LIST(c1) (
    -> PARTITION p0 VALUES IN (0, 3, 6),
    -> PARTITION p1 VALUES IN (1, 4, 7, NULL),
    -> PARTITION p2 VALUES IN (2, 5, 8)
    -> );
Query OK, 0 rows affected (0.02 sec)

在为分区定义值列表时,你可以(也应该)像对待其他值一样对待NULL。例如,(NULL)和(1,4,7,NULL)中的值都是有效的,(1,NULL, 4, 7)、(NULL, 1, 4, 7)等的值也是有效的。你可以在表ts2和ts3中分别插入列c1为NULL的行:

mysql> INSERT INTO ts2 VALUES (NULL, 'mothra');
Query OK, 1 row affected (0.00 sec)

mysql> INSERT INTO ts3 VALUES (NULL, 'mothra');
Query OK, 1 row affected (0.00 sec)

通过对INFORMATION_SCHEMA.PARTITIONS发出适当的查询。你可以确定哪些分区用于存储刚刚插入的行(和前面的例子一样,我们假设分区表是 在test数据库中创建的):

mysql> SELECT TABLE_NAME, PARTITION_NAME, TABLE_ROWS, AVG_ROW_LENGTH, DATA_LENGTH  FROM INFORMATION_SCHEMA.PARTITIONS WHERE  TABLE_SCHEMA = 'test' AND TABLE_NAME LIKE 'ts_';
+------------+----------------+------------+----------------+-------------+
| TABLE_NAME | PARTITION_NAME | TABLE_ROWS | AVG_ROW_LENGTH | DATA_LENGTH |
+------------+----------------+------------+----------------+-------------+
| ts2        | p0             |          0 |              0 |       16384 |
| ts2        | p1             |          0 |              0 |       16384 |
| ts2        | p2             |          0 |              0 |       16384 |
| ts2        | p3             |          1 |          16384 |       16384 |
| ts3        | p0             |          0 |              0 |       16384 |
| ts3        | p1             |          1 |          16384 |       16384 |
| ts3        | p2             |          0 |              0 |       16384 |
+------------+----------------+------------+----------------+-------------+
7 rows in set (0.01 sec)

如前面所示,您还可以通过删除这些分区,然后执行SELECT来验证哪些分区用于存储行。

哈希和键分区处理NULL。
对于按哈希分区的表,对NULL的处理有些不同。在这些情况下,任何产生NULL值的分区表达式都将被视为其返回值为零。我们可以通过检查创建 按哈希分区的表并使用包含适当值的记录填充表对文件系统的影响来验证这种行为。假设你用下面的语句创建了一个表th(也在test数据库中) :

mysql> CREATE TABLE th (
    -> c1 INT,
    -> c2 VARCHAR(20)
    -> )
    -> PARTITION BY HASH(c1)
    -> PARTITIONS 2;
Query OK, 0 rows affected (0.01 sec)

属于该表的分区可以使用下面的查询来查看:

mysql> SELECT TABLE_NAME, PARTITION_NAME, TABLE_ROWS, AVG_ROW_LENGTH, DATA_LENGTH  FROM INFORMATION_SCHEMA.PARTITIONS WHERE  TABLE_SCHEMA = 'test' AND TABLE_NAME='th';
+------------+----------------+------------+----------------+-------------+
| TABLE_NAME | PARTITION_NAME | TABLE_ROWS | AVG_ROW_LENGTH | DATA_LENGTH |
+------------+----------------+------------+----------------+-------------+
| th         | p0             |          0 |              0 |       16384 |
| th         | p1             |          0 |              0 |       16384 |
+------------+----------------+------------+----------------+-------------+
2 rows in set (0.00 sec)

每个分区的TABLE_ROWS为0。现在插入两行,c1列的值分别为NULL和0,并验证这两行是否被插入,如下所示:

mysql> INSERT INTO th VALUES (NULL, 'mothra'), (0, 'gigan');
Query OK, 2 rows affected (0.00 sec)
Records: 2  Duplicates: 0  Warnings: 0

mysql> SELECT * FROM th;
+------+--------+
| c1   | c2     |
+------+--------+
| NULL | mothra |
|    0 | gigan  |
+------+--------+
2 rows in set (0.00 sec)

mysql>

回想一下,对于任何整数N, NULL MOD N的值总是NULL。对于按哈希或键进行分区的表,将该结果作为确定正确分区的0处理。再次查询 INFORMATION_SCHEMA.PARTITIONS表,我们可以看到,两行都插入到了分区p0中:

mysql> SELECT TABLE_NAME, PARTITION_NAME, TABLE_ROWS, AVG_ROW_LENGTH, DATA_LENGTH  FROM INFORMATION_SCHEMA.PARTITIONS WHERE  TABLE_SCHEMA = 'test' AND TABLE_NAME='th';
+------------+----------------+------------+----------------+-------------+
| TABLE_NAME | PARTITION_NAME | TABLE_ROWS | AVG_ROW_LENGTH | DATA_LENGTH |
+------------+----------------+------------+----------------+-------------+
| th         | p0             |          2 |           8192 |       16384 |
| th         | p1             |          0 |              0 |       16384 |
+------------+----------------+------------+----------------+-------------+
2 rows in set (0.00 sec)

键分区处理NULL。
对于按键分区的表,对NULL的处理有些不同。在这些情况下,任何产生NULL值的分区表达式都将被视为其返回值为NULL。我们可以通过检查创建 按键分区的表并使用包含适当值的记录填充表对文件系统的影响来验证这种行为。假设你用下面的语句创建了一个表tk(也在test数据库中):

mysql> CREATE TABLE tk (
    -> c1 INT NOT NULL PRIMARY KEY,
    -> c2 VARCHAR(20)
    -> )
    -> PARTITION BY KEY()
    -> PARTITIONS 2;
Query OK, 0 rows affected (0.02 sec)

属于该表的分区可以使用下面的查询来查看:

mysql> SELECT TABLE_NAME, PARTITION_NAME, TABLE_ROWS, AVG_ROW_LENGTH, DATA_LENGTH  FROM INFORMATION_SCHEMA.PARTITIONS WHERE  TABLE_SCHEMA = 'test' AND TABLE_NAME='tk';
+------------+----------------+------------+----------------+-------------+
| TABLE_NAME | PARTITION_NAME | TABLE_ROWS | AVG_ROW_LENGTH | DATA_LENGTH |
+------------+----------------+------------+----------------+-------------+
| tk         | p0             |          0 |              0 |       16384 |
| tk         | p1             |          0 |              0 |       16384 |
+------------+----------------+------------+----------------+-------------+
2 rows in set (0.00 sec)

每个分区的TABLE_ROWS为0。现在插入两行,c1列的值分别为NULL和0,并验证这两行是否被插入,如下所示:

mysql> INSERT INTO tk VALUES (NULL, 'mothra'), (0, 'gigan');
ERROR 1048 (23000): Column 'c1' cannot be null

MySQL 5.7 子分区

子分区
子分区(也称为复合分区)是分区表中每个分区的进一步划分。请看下面的CREATE TABLE语句:

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

表ts有3个范围分区。每个分区(p0、p1和p2)又进一步划分为2个子分区。实际上,整个表被分为3 * 2 = 6个分区。但是,由于PARTITION BY RANGE子句的作用,前2个只存储所购列中值小于1990的记录。

在MySQL 5.7中,可以对按范围或列表分区的表进行子分区。子分区可以使用哈希分区或键分区。这也称为复合分区。

也可以使用子分区子句显式定义子分区,为各个子分区指定选项。例如,在前面的例子中,创建相同的表ts的一种更冗长的方式是:

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

下面列出了一些语法注意事项:
.每个分区必须有相同数量的子分区。

.如果您在分区表的任何分区上使用SUBPARTITION显式地定义了任何子分区,则必须定义所有子分区。换句话说,下面的语句会失败:

mysql> CREATE TABLE ts1 (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),
    -> PARTITION p2 VALUES LESS THAN MAXVALUE (
    -> SUBPARTITION s2,
    -> SUBPARTITION s3
    -> )
    -> );
ERROR 1064 (42000): Wrong number of subpartitions defined, mismatch with previous setting near '
PARTITION p2 VALUES LESS THAN MAXVALUE (
SUBPARTITION s2,
SUBPARTITION s3
)
)' at line 8

即使包含SUBPARTITIONS 2子句,这条语句仍然会失败。

.每个子分区子句必须(至少)包含子分区的名称。否则,您可以为子分区设置任何想要的选项,或者允许它假定该选项的默认设置。

.子分区名在整个表中必须是唯一的。例如,下面的CREATE TABLE语句在MySQL 5.7中是有效的:

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
)
);

子分区可以用于特别大的MyISAM表,以便在许多磁盘上分发数据和索引。假设您有6个磁盘挂载为/disk0、/disk1、/disk2等。现在考虑下面的例子:

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'
)
);

在这种情况下,为每个RANGE的数据和索引使用一个单独的磁盘。许多其他的变化是可能的;另一个例子可能是:

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 s0a
DATA DIRECTORY = '/disk0'
INDEX DIRECTORY = '/disk1',
SUBPARTITION s0b
DATA DIRECTORY = '/disk2'
INDEX DIRECTORY = '/disk3'
),
PARTITION p1 VALUES LESS THAN (2000) (
SUBPARTITION s1a
DATA DIRECTORY = '/disk4/data'
INDEX DIRECTORY = '/disk4/idx',
SUBPARTITION s1b
DATA DIRECTORY = '/disk5/data'
INDEX DIRECTORY = '/disk5/idx'
),
PARTITION p2 VALUES LESS THAN MAXVALUE (
SUBPARTITION s2a,
SUBPARTITION s2b
)
);

这里,存储如下:
.购买日期在1990年之前的行占用了大量的空间,因此以4种方式拆分,一个单独的磁盘专门用于存储数据和两个子分区(s0a和s0b)的索引,这 两个分区组成了p0分区。换句话说:
子分区s0a的数据存储在/disk0上。

.子分区s0a的数据存储在/disk0上。
.子分区s0a的索引存储在/disk1上。
.子分区s0b的数据存储在/disk2上。
.子分区s0b的索引存储在/disk3上。

.包含从1990年到1999年的日期的行(分区p1)不需要像1990年以前的行那样多的空间。它们被划分为2个磁盘(/disk4和/disk5),而不是像以 前那样存储在p0中的4个磁盘。

.属于p1的第一个子分区(s1a)的数据和索引存储在/disk4中,其中数据存储在/disk4/ Data中,索引存储在/disk4/idx中。
.属于p1的第二个子分区(s1b)的数据和索引存储在/disk5中——数据存储在/disk5/ Data中,索引存储在/disk5/idx中。

.反映2000年至今日期的行(分区p2)所占空间与前两个范围相比都没有那么大。目前,将所有这些都存储在默认位置就足够了。

将来,当从2000年开始的十年的购买数量增长到默认位置不再提供足够的空间时,可以使用ALTER TABLE … REORGANIZE PARTITION语句移动相 应的行。

MySQL 5.7 键分区

键分区
按键分区类似于按哈希分区,不同的是,哈希分区采用了用户定义的表达式,用于键分区的哈希函数由MySQL服务器提供。NDB集群使用MD5() 来实现这个目的。对于使用其他存储引擎的表,服务器使用自己的内部哈希函数,该函数基于与PASSWORD()相同的算法。

CREATE TABLE … PARTITION BY KEY的语法规则类似于按哈希分区的表。主要区别如下:
.使用KEY而不是HASH

.KEY只接受一个包含零个或多个列名的列表。任何用作分区键的列都必须包含表的主键(如果表有主键的话)的一部分或全部。如果没有指定列 名作为分区键,则使用表的主键(如果有主键的话)。例如,下面的CREATE TABLE语句在MySQL 5.7中是有效的:

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

如果没有主键,但有一个唯一键,那么这个唯一键用于分区键:

mysql> CREATE TABLE k1 (
    -> id INT NOT NULL,
    -> name VARCHAR(20),
    -> UNIQUE KEY (id)
    -> )
    -> PARTITION BY KEY()
    -> PARTITIONS 2;
Query OK, 0 rows affected (0.01 sec)

但是,如果唯一键列没有定义为not NULL,那么前面的语句将失败。

在这两种情况下,分区键都是id列,尽管它没有显示在SHOW CREATE TABLE的输出中或INFORMATION_SCHEMA.PARTITIONS表的 PARTITION_EXPRESSION列中。

与其他分区类型不同的是,用于按键分区的列并不局限于整数值或NULL值。例如,下面的CREATE TABLE语句是有效的:

mysql> CREATE TABLE tm1 (
    -> s1 CHAR(32) PRIMARY KEY
    -> )
    -> PARTITION BY KEY(s1)
    -> PARTITIONS 10;
Query OK, 0 rows affected (0.04 sec)

如果指定了不同的分区类型,上述语句将无效。(在这种情况下,简单地使用按键分区()也是有效的,并且与按键分区(s1)的效果相同,因 为s1是表的主键。)

也可以通过线性键对表进行分区。这里有一个简单的例子:

mysql> CREATE TABLE tk (
    -> col1 INT NOT NULL,
    -> col2 CHAR(5),
    -> col3 DATE
    -> )
    -> PARTITION BY LINEAR KEY (col1)
    -> PARTITIONS 3;
Query OK, 0 rows affected (0.02 sec)

使用LINEAR对键分区的效果与对哈希分区的效果相同,分区数是使用2的幂算法而不是模运算得到的。

MySQL 5.7 哈希分区

哈希分区
哈希分区主要用于确保数据在预定数量的分区中均匀分布。使用范围分区或列表分区时,用户必须明确指定给定的列值或一组列值存储在哪个分 区中;使用哈希分区时,MySQL会帮你处理这个问题,你只需要根据要哈希的列值和要划分的分区数指定一个列值或表达式。

要使用哈希分区对表进行分区,需要在CREATE table语句中添加一个partition BY HASH (expr)子句,其中expr是一个返回整数的表达式。这 可以是MySQL整数类型的列的名称。此外,你很可能想在接下来使用分区num,其中num是一个正整数,表示表被划分的分区数。

为简单起见,下面示例中的表不使用任何键。你应该注意,如果一张表有任何唯一键,那么在这个表的分区表达式中使用的每一列都必须是每个 唯一键的一部分,包括主键。

下面的语句创建了一个在store_id列上使用哈希的表,它被分为4个分区:

mysql> CREATE TABLE employees (
    -> id INT NOT NULL,
    -> fname VARCHAR(30),
    -> lname VARCHAR(30),
    -> hired DATE NOT NULL DEFAULT '1970-01-01',
    -> separated DATE NOT NULL DEFAULT '9999-12-31',
    -> job_code INT,
    -> store_id INT
    -> )
    -> PARTITION BY HASH(store_id)
    -> PARTITIONS 4;
Query OK, 0 rows affected (0.01 sec)

如果不包含PARTITIONS子句,则分区的数量默认为1。

使用PARTITIONS关键字而后面没有数字会导致语法错误。

你也可以使用在expr中返回整数的SQL表达式。例如,你可能想根据雇员受雇的年份进行分区。可以这样做:

mysql> CREATE TABLE employees (
    -> id INT NOT NULL,
    -> fname VARCHAR(30),
    -> lname VARCHAR(30),
    -> hired DATE NOT NULL DEFAULT '1970-01-01',
    -> separated DATE NOT NULL DEFAULT '9999-12-31',
    -> job_code INT,
    -> store_id INT
    -> )
    -> PARTITION BY HASH( YEAR(hired) )
    -> PARTITIONS 4;
Query OK, 0 rows affected (0.02 sec)

expr必须返回一个非常量、非随机的整数值(换句话说,它应该是可变的,但是确定的),而且不能包含任何禁止使用的结构。你还应该记住, 每当插入或更新(或删除)一行时,这个表达式都会被计算;这意味着非常复杂的表达式可能会导致性能问题,特别是在执行一次影响很多行的 操作(如批量插入)时。

最高效的散列函数是对表中的单个列进行操作,其值随列值的增加或减少而增加或减少,因为这允许在分区范围上进行“修剪”。也就是说,该 表达式与所基于的列的值变化越密切,MySQL使用该表达式进行哈希分区的效率就越高。

例如,如果 date_col 是 DATE 类型的列,那么表达式 TO_DAYS(date_col) 就被认为与 date_col 的值成正比变化,因为 date_col 的值每发 生一次变化,该表达式的值都会以一致的方式变化。表达式 YEAR(date_col) 相对于 date_col 的变化不像 TO_DAYS(date_col) 那样直接,因 为 date_col 的每个可能变化都不会在 YEAR(date_col) 中产生同等的变化。即便如此,YEAR(date_col) 仍是一个不错的哈希函数候选,因为 它与 date_col 的一部分成正比变化,并且 date_col 的任何变化都不会在 YEAR(date_col) 中产生不成比例的变化。

相比之下,假设你有一个名为int_col的列,其类型为INT。现在考虑表达式POW(5-int_col,3) + 6。对于哈希函数来说,这是一个糟糕的选择, 因为改变int_col的值并不一定会导致表达式的值成比例地改变。将int_col的值改变一个给定的值,可能会导致表达式的值发生很大不同的变化 。例如,将int_col从5改为6,表达式的值会改变-1,而将int_col的值从6改为7,表达式的值会改变-7。

换句话说,列值的图形与表达式的值越接近,就像方程y=cx所示的直线,其中c是某个非零常数,表达式越适合散列。这是因为表达式越非线性 ,其产生的分区之间的数据分布就越不均匀。

理论上,对于涉及多个列值的表达式,也可以进行“修剪”,但确定哪一个表达式适合是非常困难和耗时的。因此,不推荐使用涉及多列的散列 表达式。

当使用PARTITION BY HASH时,MySQL根据表达式结果的模值来确定使用num个分区中的哪个分区。换句话说,对于给定的表达式expr,存储记录 的分区是分区号N,其中N = MOD(expr, num)。假设表t1定义如下,它有4个分区:

mysql> CREATE TABLE t1 (col1 INT, col2 CHAR(5), col3 DATE)
    -> PARTITION BY HASH( YEAR(col3) )
    -> PARTITIONS 4;
Query OK, 0 rows affected (0.03 sec)

如果你在t1中插入一条col3值为‘2005-09-15’的记录,那么存储它的分区如下所示:

MOD(YEAR('2005-09-01'),4)
= MOD(2005,4)
= 1

MySQL 5.7还支持哈希分区的一种变体,称为线性哈希,它采用一种更复杂的算法来确定插入到分区表中的新行的位置。

每次插入或更新记录时,都会对用户提供的表达式进行计算。根据具体情况,还可以在删除记录时对其进行评估。

线性哈希分区
MySQL还支持线性哈希,它与常规哈希的不同之处在于,线性哈希利用线性2次幂算法,而常规哈希使用哈希函数值的模数。

从语法上讲,线性哈希分区和常规哈希之间的唯一区别是添加了在PARTITION BY子句中添加LINEAR关键字,如下所示:

mysql> CREATE TABLE employees (
    -> id INT NOT NULL,
    -> fname VARCHAR(30),
    -> lname VARCHAR(30),
    -> hired DATE NOT NULL DEFAULT '1970-01-01',
    -> separated DATE NOT NULL DEFAULT '9999-12-31',
    -> job_code INT,
    -> store_id INT
    -> )
    -> PARTITION BY LINEAR HASH( YEAR(hired) )
    -> PARTITIONS 4;
Query OK, 0 rows affected (0.03 sec)

给定表达式expr,使用线性哈希时存储记录的分区为num个分区中的第N个分区,其中N根据以下算法推导:
1.求下一个大于num的2次幂,我们称这个值为V;可计算为:

V = POWER(2, CEILING(LOG(2, num)))

(假设num是13。那么LOG(2,13)为3.7004397181411。CEILING(3.7004397181411)等于4,V = POWER(2,4),等于16。

2.设N = F(column_list) & (V – 1)。

3.当N >= num:
.设V = V / 2
.设N = N & (V – 1)

假设表t1使用线性哈希分区,有6个分区,使用下面的语句创建:

mysql> CREATE TABLE t1 (col1 INT, col2 CHAR(5), col3 DATE)
    -> PARTITION BY LINEAR HASH( YEAR(col3) )
    -> PARTITIONS 6;
Query OK, 0 rows affected (0.03 sec)

现在假设你想在t1中插入两条col3列值为‘2003-04-14’和‘1998-10-19’的记录。第一个分区的分区号确定如下:

V = POWER(2, CEILING( LOG(2,6) )) = 8
N = YEAR('2003-04-14') & (8 - 1)
= 2003 & 7
= 3
(3 >= 6 is FALSE: record stored in partition #3)

存储第二条记录的分区编号计算如下所示:

V = 8
N = YEAR('1998-10-19') & (8 - 1)
= 1998 & 7
= 6
(6 >= 6 is TRUE: additional step required)
N = 6 & ((8 / 2) - 1)
= 6 & 3
= 2


(2 >= 6 is FALSE: record stored in partition #2)

通过线性哈希进行分区的优点是,分区的添加、删除、合并和拆分都快得多,这在处理包含大量(tb级)数据的表时非常有用。缺点是,与使用 常规哈希分区获得的分布相比,数据不太可能在分区之间均匀分布。

MySQL 5.7 列表列分区

列表列分区
MySQL 5.7支持列表列分区。这是列表分区的一个变体,允许使用多个列作为分区键,并允许非整数类型的数据类型用作分区列;你可以使用字 符串类型、DATE和DATETIME列。

使用列表列分区,用户可以为客户数据创建一个表,根据客户所在城市的名称,将一行分配给对应于这些区域的4个分区中的任何一个,如下所示:

mysql> CREATE TABLE customers_1 (
    -> first_name VARCHAR(25),
    -> last_name VARCHAR(25),
    -> street_1 VARCHAR(30),
    -> street_2 VARCHAR(30),
    -> city VARCHAR(15),
    -> renewal DATE
    -> )
    -> PARTITION BY LIST COLUMNS(city) (
    -> PARTITION pRegion_1 VALUES IN('Oskarshamn', 'H?gsby', 'M?nster?s'),
    -> PARTITION pRegion_2 VALUES IN('Vimmerby', 'Hultsfred', 'V?stervik'),
    -> PARTITION pRegion_3 VALUES IN('N?ssj?', 'Eksj?', 'Vetlanda'),
    -> PARTITION pRegion_4 VALUES IN('Uppvidinge', 'Alvesta', 'V?xjo')
    -> );
Query OK, 0 rows affected (0.04 sec)

与按范围分区列一样,你不需要在COLUMNS()子句中使用表达式将列值转换为整数。(实际上,COLUMNS()不允许使用列名以外的表达式。)

也可以使用DATE和DATETIME列,如下面的例子所示,它们的名称和列与之前的customers_1表相同,但使用基于renew列的列表列分区将行存储在 4个分区中的一个中,具体取决于2010年2月客户账户计划更新的那一周:

mysql> CREATE TABLE customers_2 (
    -> first_name VARCHAR(25),
    -> last_name VARCHAR(25),
    -> street_1 VARCHAR(30),
    -> street_2 VARCHAR(30),
    -> city VARCHAR(15),
    -> renewal DATE
    -> )
    -> PARTITION BY LIST COLUMNS(renewal) (
    -> PARTITION pWeek_1 VALUES IN('2010-02-01', '2010-02-02', '2010-02-03',
    -> '2010-02-04', '2010-02-05', '2010-02-06', '2010-02-07'),
    -> PARTITION pWeek_2 VALUES IN('2010-02-08', '2010-02-09', '2010-02-10',
    -> '2010-02-11', '2010-02-12', '2010-02-13', '2010-02-14'),
    -> PARTITION pWeek_3 VALUES IN('2010-02-15', '2010-02-16', '2010-02-17',
    -> '2010-02-18', '2010-02-19', '2010-02-20', '2010-02-21'),
    -> PARTITION pWeek_4 VALUES IN('2010-02-22', '2010-02-23', '2010-02-24',
    -> '2010-02-25', '2010-02-26', '2010-02-27', '2010-02-28')
    -> );
Query OK, 0 rows affected (0.02 sec)

这是可行的,但如果涉及的日期数量非常大,则定义和维护起来就很麻烦。在这种情况下,采用范围或范围列分区通常更实用。在本例中,由于 我们希望用作分区键的列是日期列,所以我们使用范围列分区,如下所示:

mysql> CREATE TABLE customers_3 (
    -> first_name VARCHAR(25),
    -> last_name VARCHAR(25),
    -> street_1 VARCHAR(30),
    -> street_2 VARCHAR(30),
    -> city VARCHAR(15),
    -> renewal DATE
    -> )
    -> PARTITION BY RANGE COLUMNS(renewal) (
    -> PARTITION pWeek_1 VALUES LESS THAN('2010-02-09'),
    -> PARTITION pWeek_2 VALUES LESS THAN('2010-02-15'),
    -> PARTITION pWeek_3 VALUES LESS THAN('2010-02-22'),
    -> PARTITION pWeek_4 VALUES LESS THAN('2010-03-01')
    -> );
Query OK, 0 rows affected (0.03 sec)

此外(与范围列分区一样),你可以在COLUMNS()子句中使用多个列。

MySQL 5.7 范围列分区

范围列分区
范围列分区类似于范围分区,但允许您使用基于多个列值的范围来定义分区。此外,还可以使用非整数类型的列来定义范围。

范围列分区与范围分区在以下几个方面有很大的不同:
.范围列分区不接受表达式,只接受列名。

.范围列分区接受一个或多个列的列表。
范围列分区基于元组(列值列表)之间的比较,而不是标量值之间的比较。在范围列分区中行的放置也基于元组之间的比较;本节后面将进一步 讨论这一点。

.范围列分区列不限制为整数列;string、DATE和DATETIME列也可以用作分区列。

创建由范围列分区的表的基本语法如下所示:

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][, ...]

在上述语法中,column_list是一个包含一个或多个列的列表(有时称为分区列列表),value_list是一个值列表(也就是说,它是一个分区定 义值列表)。每个分区定义都必须提供一个value_list,每个value_list的值数量必须与column_list的列数量相同。一般来说,如果在columns 子句中使用N列,那么每个VALUES小于子句也必须提供N个值的列表。

分区列列表中的元素以及定义每个分区的值列表中的元素必须按相同的顺序出现。此外,值列表中的每个元素必须与列列表中对应的元素具有相 同的数据类型。不过,分区列列表中的列名顺序以及值列表中的顺序不必与 CREATE TABLE 语句主部分中的表列定义顺序相同。与按范围分区的 表一样,您可以使用 MAXVALUE 来表示一个值,使得插入给定列中的任何合法值始终小于此值。以下是一个 CREATE TABLE 语句的示例,有助于 说明所有这些要点:

mysql> CREATE TABLE rcx (
    -> a INT,
    -> b INT,
    -> c CHAR(3),
    -> d INT
    -> )
    -> PARTITION BY RANGE COLUMNS(a,d,c) (
    -> PARTITION p0 VALUES LESS THAN (5,10,'ggg'),
    -> PARTITION p1 VALUES LESS THAN (10,20,'mmm'),
    -> PARTITION p2 VALUES LESS THAN (15,30,'sss'),
    -> PARTITION p3 VALUES LESS THAN (MAXVALUE,MAXVALUE,MAXVALUE)
    -> );
Query OK, 0 rows affected (0.03 sec)

rcx表包含a、b、c、d列。提供给columns子句的分区列列表使用了其中3个列,按a、d、c的顺序。每个用于定义分区的值列表包含3个相同顺序 的值;也就是说,每个值列表元组的形式都是(INT, INT, CHAR(3)),对应于a、d、c列使用的数据类型(按此顺序)。

将行放置到分区中是通过比较将要插入的行的元组来确定的,该元组与COLUMNS子句中的列列表匹配,并用于定义表的分区的VALUES LESS THAN 子句。因为我们比较的是元组(即列表或值的集合)而不是标量值,所以范围列分区使用的values LESS than的语义与简单范围分区的情况有所 不同。在范围分区中,生成的表达式值等于VALUES小于中的一个极限值的行永远不会被放置到相应的分区中;然而,当使用范围列分区时,有时 可能会出现这样的情况:分区列列表的第一个元素的值与小于值列表的第一个元素的值相等,那么就被放置到相应的分区中。

考虑下面这个语句创建的范围分区表

mysql> CREATE TABLE r1 (
    -> a INT,
    -> b INT
    -> )
    -> PARTITION BY RANGE (a) (
    -> PARTITION p0 VALUES LESS THAN (5),
    -> PARTITION p1 VALUES LESS THAN (MAXVALUE)
    -> );
Query OK, 0 rows affected (0.02 sec)

如果我们在这个表中插入3行,使得a的每一行的列值为5,那么所有3行都存储在分区p1中,因为在每种情况下a列的值都不小于5,这可以通过对 INFORMATION_SCHEMA执行适当的查询来看到。分区表:

mysql> INSERT INTO r1 VALUES (5,10), (5,11), (5,12);
Query OK, 3 rows affected (0.00 sec)
Records: 3  Duplicates: 0  Warnings: 0

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

现在考虑一个类似的表rc1,它使用范围列分区,在COLUMNS子句中同时引用a和b列,如下所示:

mysql> CREATE TABLE rc1 (
    -> a INT,
    -> b INT
    -> )
    -> PARTITION BY RANGE COLUMNS(a, b) (
    -> PARTITION p0 VALUES LESS THAN (5, 12),
    -> PARTITION p3 VALUES LESS THAN (MAXVALUE, MAXVALUE)
    -> );
Query OK, 0 rows affected (0.01 sec)

如果我们在rc1中插入与r1中完全相同的行,那么行的分布就会大不相同:

mysql> INSERT INTO rc1 VALUES (5,10), (5,11), (5,12);
Query OK, 3 rows affected (0.00 sec)
Records: 3  Duplicates: 0  Warnings: 0

mysql> SELECT PARTITION_NAME,TABLE_ROWS
    -> FROM INFORMATION_SCHEMA.PARTITIONS
    -> WHERE TABLE_NAME = 'rc1';
+----------------+------------+
| PARTITION_NAME | TABLE_ROWS |
+----------------+------------+
| p0             |          2 |
| p3             |          1 |
+----------------+------------+
2 rows in set (0.00 sec)

这是因为我们比较的是行而不是标量值。我们可以将插入的行值与表rc1中用于定义分区p0的values THAN LESS THAN子句中的限制行值进行比较 ,如下所示:

mysql> SELECT (5,10) < (5,12), (5,11) < (5,12), (5,12) < (5,12);
+-----------------+-----------------+-----------------+
| (5,10) < (5,12) | (5,11) < (5,12) | (5,12) < (5,12) |
+-----------------+-----------------+-----------------+
|               1 |               1 |               0 |
+-----------------+-----------------+-----------------+
1 row in set (0.00 sec)

两个元组(5,10)和(5,11)的值小于(5,12),因此它们被存储在p0分区中。由于5不小于5,12不小于12,因此(5,12)被认为不小于(5,12 ),并存储在分区p1中。

上例中的SELECT语句也可以使用显式的行构造函数编写,如下所示:

mysql> SELECT ROW(5,10) < ROW(5,12), ROW(5,11) < ROW(5,12), ROW(5,12) < ROW(5,12);
+-----------------------+-----------------------+-----------------------+
| ROW(5,10) < ROW(5,12) | ROW(5,11) < ROW(5,12) | ROW(5,12) < ROW(5,12) |
+-----------------------+-----------------------+-----------------------+
|                     1 |                     1 |                     0 |
+-----------------------+-----------------------+-----------------------+
1 row in set (0.00 sec)

对于只使用单个分区列的按范围列分区的表,在分区中存储行与按范围分区的表是一样的。下面的CREATE TABLE语句使用1个分区列创建一个按 范围列分区的表:

mysql> CREATE TABLE rx (
    -> a INT,
    -> b INT
    -> )
    -> PARTITION BY RANGE COLUMNS (a) (
    -> PARTITION p0 VALUES LESS THAN (5),
    -> PARTITION p1 VALUES LESS THAN (MAXVALUE)
    -> );
Query OK, 0 rows affected (0.01 sec)

如果我们将(5,10),(5,11)和(5,12)行插入到这个表中,我们可以看到它们的位置与我们之前创建和填充的表r的位置相同:

mysql> INSERT INTO rx VALUES (5,10), (5,11), (5,12);
Query OK, 3 rows affected (0.01 sec)
Records: 3  Duplicates: 0  Warnings: 0

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

还可以创建按范围列进行分区的表,其中一个或多个列的限制值在连续的分区定义中重复出现。只要用于定义分区的列值元组严格递增,就可以 这样做。例如,下面的CREATE TABLE语句都是有效的。

mysql> CREATE TABLE rc2 (
    -> a INT,
    -> b INT
    -> )
    -> PARTITION BY RANGE COLUMNS(a,b) (
    -> PARTITION p0 VALUES LESS THAN (0,10),
    -> PARTITION p1 VALUES LESS THAN (10,20),
    -> PARTITION p2 VALUES LESS THAN (10,30),
    -> PARTITION p3 VALUES LESS THAN (MAXVALUE,MAXVALUE)
    -> );
Query OK, 0 rows affected (0.01 sec)

mysql> CREATE TABLE rc3 (
    -> a INT,
    -> b INT
    -> )
    -> PARTITION BY RANGE COLUMNS(a,b) (
    -> PARTITION p0 VALUES LESS THAN (0,10),
    -> PARTITION p1 VALUES LESS THAN (10,20),
    -> PARTITION p2 VALUES LESS THAN (10,30),
    -> PARTITION p3 VALUES LESS THAN (10,35),
    -> PARTITION p4 VALUES LESS THAN (20,40),
    -> PARTITION p5 VALUES LESS THAN (MAXVALUE,MAXVALUE)
    -> );
Query OK, 0 rows affected (0.02 sec)

下面的语句也成功了,尽管乍一看可能不会成功,因为分区p0的b列的极限值是25,分区p1的极限值是20,分区p1的c列的极限值是100,分区p2 的极限值是50:

mysql> CREATE TABLE rc4 (
    -> a INT,
    -> b INT,
    -> c INT
    -> )
    -> PARTITION BY RANGE COLUMNS(a,b,c) (
    -> PARTITION p0 VALUES LESS THAN (0,25,50),
    -> PARTITION p1 VALUES LESS THAN (10,20,100),
    -> PARTITION p2 VALUES LESS THAN (10,30,50),
    -> PARTITION p3 VALUES LESS THAN (MAXVALUE,MAXVALUE,MAXVALUE)
    -> );
Query OK, 0 rows affected (0.02 sec)

在设计按范围列进行分区的表时,你总是可以使用mysql客户端比较所需的元组来测试连续的分区定义,如下所示:

mysql> SELECT (0,25,50) < (10,20,100), (10,20,100) < (10,30,50);
+-------------------------+--------------------------+
| (0,25,50) < (10,20,100) | (10,20,100) < (10,30,50) |
+-------------------------+--------------------------+
|                       1 |                        1 |
+-------------------------+--------------------------+
1 row in set (0.00 sec)

如果CREATE TABLE语句包含的分区定义不是严格按递增顺序排列的,则会失败并报错,如下例所示:

mysql> CREATE TABLE rcf (
    -> a INT,
    -> b INT,
    -> c INT
    -> )
    -> PARTITION BY RANGE COLUMNS(a,b,c) (
    -> PARTITION p0 VALUES LESS THAN (0,25,50),
    -> PARTITION p1 VALUES LESS THAN (20,20,100),
    -> PARTITION p2 VALUES LESS THAN (10,30,50),
    -> PARTITION p3 VALUES LESS THAN (MAXVALUE,MAXVALUE,MAXVALUE)
    -> );
ERROR 1493 (HY000): VALUES LESS THAN value must be strictly increasing for each partition

当出现这样的错误时,您可以通过在列列表之间进行“小于”比较来推断哪些分区定义是无效的。在这种情况下,问题出在分区p2的定义上,因 为用于定义它的元组不小于用于定义分区p3的元组,如下所示:

mysql> SELECT (0,25,50) < (20,20,100), (20,20,100) < (10,30,50);
+-------------------------+--------------------------+
| (0,25,50) < (20,20,100) | (20,20,100) < (10,30,50) |
+-------------------------+--------------------------+
|                       1 |                        0 |
+-------------------------+--------------------------+
1 row in set (0.00 sec)

在使用范围列时,同一列的MAXVALUE也可以出现在多个VALUES LESS THAN子句中。然而,在连续的分区定义中,单个列的限制值应该增加,不应 该有超过一个分区定义中有MAXVALUE,它用作所有列值的上限,该分区定义应该出现在分区列表的PARTITION ... VALUES LESS THAN子句的最后 。此外,您不能在多个分区定义中使用MAXVALUE作为第一列的限制值。

如前所述,范围列分区也可以使用非整数列作为分区列。考虑一个名为employees的表(未分区),使用以下语句创建:

CREATE TABLE employees (
id INT NOT NULL,
fname VARCHAR(30),
lname VARCHAR(30),
hired DATE NOT NULL DEFAULT '1970-01-01',
separated DATE NOT NULL DEFAULT '9999-12-31',
job_code INT NOT NULL,
store_id INT NOT NULL
);

使用范围列分区,你可以创建这个表的一个版本,根据员工的姓氏将每一行存储在4个分区中的一个中,如下所示:

mysql> CREATE TABLE employees_by_lname (
    -> id INT NOT NULL,
    -> fname VARCHAR(30),
    -> lname VARCHAR(30),
    -> hired DATE NOT NULL DEFAULT '1970-01-01',
    -> separated DATE NOT NULL DEFAULT '9999-12-31',
    -> job_code INT NOT NULL,
    -> store_id INT NOT NULL
    -> )
    -> PARTITION BY RANGE COLUMNS (lname) (
    -> PARTITION p0 VALUES LESS THAN ('g'),
    -> PARTITION p1 VALUES LESS THAN ('m'),
    -> PARTITION p2 VALUES LESS THAN ('t'),
    -> PARTITION p3 VALUES LESS THAN (MAXVALUE)
    -> );
Query OK, 0 rows affected (0.02 sec)

或者,用户也可以通过执行下面的ALTER TABLE语句,对前面创建的employees表进行分区:

mysql> ALTER TABLE employees PARTITION BY RANGE COLUMNS (lname) (
    -> PARTITION p0 VALUES LESS THAN ('g'),
    -> PARTITION p1 VALUES LESS THAN ('m'),
    -> PARTITION p2 VALUES LESS THAN ('t'),
    -> PARTITION p3 VALUES LESS THAN (MAXVALUE)
    -> );
Query OK, 0 rows affected (0.03 sec)
Records: 0  Duplicates: 0  Warnings: 0

由于不同的字符集和排序规则具有不同的排序顺序,当使用字符串列作为分区列时,使用的字符集和排序规则可能会影响给定行的按范围列分区 的表的哪个分区。此外,在创建给定的数据库、表或列之后,更改其字符集或排序规则可能会导致行分布方式的更改。例如,当使用区分大小写 的排序规则时,` and `排在` Andersen `之前,但当使用不区分大小写的排序规则时,则相反。

类似地,用户可以使用下面的ALTER table语句对employees表进行分区,根据雇员的年龄,将表中的每一行存储在几个分区中的一个中:

mysql> ALTER TABLE employees PARTITION BY RANGE COLUMNS (hired) (
    -> 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)
    -> );
Query OK, 0 rows affected (0.04 sec)
Records: 0  Duplicates: 0  Warnings: 0