### 24.3.3 用表交换分区和子分区

在 MySQL 8.0 中，可以使用表交换表分区或子分区`更改表 *`pt`* 交换分区 *`p`* 带表 *`nt`*`， 在哪里*`pt`*是分区表和*`p`*是分区还是子分区*`pt`*与未分区表交换*`nt`*，前提是下列陈述为真：

1.  桌子*`nt`*本身没有分区。

2.  桌子*`nt`*不是临时表。

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

4.  桌子`nt`不包含外键引用，并且没有其他表有任何外键引用`nt`.

5.  中没有行*`nt`*位于分区定义的边界之外*`p`*.此条件不适用，如果`未经验证`用来。

6.  两个表必须使用相同的字符集和排序规则。

7.  为了`InnoDB`表，两个表必须使用相同的行格式。确定行格式`InnoDB`表，查询[`INFORMATION_SCHEMA.INNODB_TABLES`](information-schema-innodb-tables-table.html).

8.  任何分区级别`MAX_ROWS`设置为`p`必须与表级相同`MAX_ROWS`值设置为`nt`.任何分区级别的设置`MIN_ROWS`设置为`p`任何表级也必须相同`MIN_ROWS`值设置为`nt`.

    这在任何一种情况下都是正确的`pt`有一个明确的表级`MAX_ROWS`要么`MIN_ROWS`选项生效。

9.  这`AVG_ROW_LENGTH`两个表之间不能有区别`pt`和`nt`.

10. `pt`没有任何分区使用`数据目录`选项。该限制被取消`InnoDB`MySQL 8.0.14 及更高版本中的表。

11. `索引目录`表和要与之交换的分区之间不能有区别。

12. 没有表或分区`表空间`选项可以在任一表中使用。

    除了[`改变`](privileges-provided.html#priv_alter), [`插入`](privileges-provided.html#priv_insert)， 和[`创建`](privileges-provided.html#priv_create)通常需要的特权[`更改表`](alter-table.html)陈述，你必须有[`降低`](privileges-provided.html#priv_drop)执行权[`ALTER TABLE ...交换分区`](alter-table.html).

    您还应该注意以下影响[`ALTER TABLE ...交换分区`](alter-table.html)：

-   执行[`ALTER TABLE ...交换分区`](alter-table.html)不会在分区表或要交换的表上调用任何触发器。

-   任何`自动递增`交换表中的列被重置。

-   这`忽视`关键字与一起使用时无效`ALTER TABLE ...交换分区`.

    的语法[`ALTER TABLE ...交换分区`](alter-table.html)显示在这里，其中*`pt`*是分区表，*`p`*是要交换的分区（或子分区），并且*`nt`*是要与之交换的非分区表*`p`*：


```
ALTER TABLE pt
    EXCHANGE PARTITION p
    WITH TABLE nt;
```

或者，您可以附加`通过验证`要么`未经验证`.什么时候`未经验证`被指定，[`ALTER TABLE ...交换分区`](alter-table.html)在将分区交换为非分区表时，操作不会执行任何逐行验证，从而允许数据库管理员负责确保行在分区定义的边界内。`通过验证`是默认值。

一个且只有一个分区或子分区可以与一个且只有一个非分区表交换[`更改表交换分区`](alter-table.html)陈述。要交换多个分区或子分区，请使用多个[`更改表交换分区`](alter-table.html)陈述。`交换分区`不得与其他组合[`更改表`](alter-table.html)选项。分区表使用的分区和（如果适用）子分区可以是 MySQL 8.0 支持的任何类型或类型。

#### 用非分区表交换分区

假设一个分区表`e`已使用以下 SQL 语句创建和填充：

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

INSERT INTO e VALUES
    (1669, "Jim", "Smith"),
    (337, "Mary", "Jones"),
    (16, "Frank", "White"),
    (2005, "Linda", "Black");
```

现在我们创建一个未分区的副本`e`命名为`e2`.这可以使用[**mysql**](mysql.html)客户端如下所示：

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

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

可以查看表中有哪些分区`e`通过查询包含行[`INFORMATION_SCHEMA.PARTITIONS`](information-schema-partitions-table.html)表，像这样：

```
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 |
+----------------+------------+
2 rows in set (0.00 sec)
```

笔记

对于分区`InnoDB`表中给出的行数`TABLE_ROWS`的列[`INFORMATION_SCHEMA.PARTITIONS`](information-schema-partitions-table.html)table 只是 SQL 优化中使用的估计值，并不总是准确的。

交换分区`p0`在表中`e`带桌子`e2`， 您可以使用[`更改表`](alter-table-partition-operations.html)，如此处所示：

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

更准确地说，刚刚发出的语句会导致在分区中找到的任何行与在表中找到的行进行交换。您可以通过查询[`INFORMATION_SCHEMA.PARTITIONS`](information-schema-partitions-table.html)表，和以前一样。先前在分区中找到的表行`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`，确保该行存储在分区中`p0`通过选择一个`id`小于 50 的列值，然后通过查询[`分区`](information-schema-partitions-table.html)桌子：

```
mysql> INSERT INTO e VALUES (41, "Michael", "Green");
Query OK, 1 row affected (0.05 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)
```

现在我们再次交换分区`p0`带桌子`e2`使用相同的[`更改表`](alter-table-partition-operations.html)如前所述：

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

以下查询的输出显示存储在分区中的表行`p0`以及存储在表中的表行`e2`, 在发布之前[`更改表`](alter-table-partition-operations.html)声明，现在换了地方：

```
mysql> SELECT * FROM e;
+------+-------+-------+
| id   | fname | lname |
+------+-------+-------+
|   16 | Frank | White |
| 1669 | Jim   | Smith |
|  337 | Mary  | Jones |
| 2005 | Linda | Black |
+------+-------+-------+
4 rows in set (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.00 sec)

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

#### 不匹配的行

您应该记住，在发出之前在非分区表中找到的任何行[`ALTER TABLE ...交换分区`](alter-table.html)语句必须满足将它们存储在目标分区中所需的条件；否则，该语句将失败。要查看这是如何发生的，首先将一行插入`e2`即在分区的分区定义的边界之外`p0`表的`e`.例如，插入一行`id`列值太大；然后，尝试再次与分区交换表：

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

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

只有`未经验证`选项将允许此操作成功：

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

当分区与包含与分区定义不匹配的行的表交换时，数据库管理员有责任修复不匹配的行，这可以使用执行[`维修台`](repair-table.html)要么[`ALTER TABLE ...修复分区`](alter-table-partition-operations.html).

#### 在没有逐行验证的情况下交换分区

为避免在与具有多行的表交换分区时进行耗时的验证，可以通过追加来跳过逐行验证步骤`未经验证`到[`ALTER TABLE ...交换分区`](alter-table-partition-operations.html)陈述。

下面的示例比较了在使用和不使用验证的情况下，将分区与非分区表交换时的执行时间差异。分区表（表`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),
);

mysql> 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.html)要么[`ALTER TABLE ...修复分区`](alter-table-partition-operations.html).

#### 用非分区表交换子分区

您还可以交换子分区表的子分区（请参阅[第 24.2.6 节，“子分区”](partitioning-subpartitions.html)) 使用非分区表[`ALTER TABLE ...交换分区`](alter-table.html)陈述。在下面的例子中，我们首先创建一个表`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 (2.76 sec)

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

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

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

虽然我们在创建表时没有明确命名任何子分区`es`，我们可以通过包含`SUBPARTITION_NAME`的列[`分区`](information-schema-partitions-table.html)表从`INFORMATION_SCHEMA`从该表中选择时，如下所示：

```
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-partition-operations.html)语句交换子分区`p3sp0`在表中`es`与非分区表`es2`：

```
mysql> ALTER TABLE es EXCHANGE PARTITION p3sp0 WITH TABLE es2;
Query OK, 0 rows affected (0.29 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.00 sec)

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

如果一个表是子分区的，那么您只能将表的一个子分区（而不是整个分区）与一个未分区的表交换，如下所示：

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

表结构以严格的方式进行比较；分区表和非分区表的列和索引的数量、顺序、名称和类型必须完全匹配。此外，两个表必须使用相同的存储引擎：

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

mysql> ALTER TABLE es3 REMOVE PARTITIONING;
Query OK, 0 rows affected (0.53 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.00 sec)

mysql> ALTER TABLE es3 ENGINE = MyISAM;
Query OK, 0 rows affected (0.15 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
```
