

# 使用 InnoDB 表空间改善 RDS for MySQL 的崩溃恢复时间
<a name="Appendix.MySQL.CommonDBATasks.Tables"></a>

MySQL 中的所有表均由表定义、数据和索引组成。MySQL 存储引擎 InnoDB 将表数据和索引存储在*表空间*中。InnoDB 创建全局共享的表空间，其中包括数据字典和其他相关元数据，并且它可能包含表数据和索引。InnoDB 还可以为每张表创建单独的表空间和分区。这些单独的表空间存储在后缀名为 .ibd 的文件中，并且每个表空间的头部包含一个唯一识别编号。

`innodb_file_per_table` 提供一个属于 MySQL 参数组的参数，名称为 Amazon RDS。这些参数控制 InnoDB 是否（通过将参数值设置为 0）向共享表空间或（通过将参数值设置为 1）向单个表空间添加新的表数据和索引。Amazon RDS 将 `innodb_file_per_table` 参数的默认值设置为 1，以便删除单独的 InnoDB 表并回收数据库实例的表所占用的存储空间。在大多数使用案例中，推荐将 `innodb_file_per_table` 参数设置为 1。

当表特别多时 (例如，当使用标准 (磁性) 或通用 SSD 存储时，有超过 1000 张表，或当使用预置的 IOPS 存储时，有超过 10000 张表)，应将 `innodb_file_per_table` 参数设置为 0。当将该参数设置为 0 时，不会创建独立表空间，这样可以节省数据库崩溃恢复所需的时间。

MySQL 在崩溃恢复循环期间处理每个元数据文件 (包括表空间)。当有多个表空间时，与 MySQL 用于处理数以千计的表空间文件的时间相比，MySQL 用于处理共享表空间中的元数据信息的时间可以忽略不计。因为表空间编号存储在每个文件的头部，读取所有表空间文件的总时间可能需要长达数小时。例如，在崩溃恢复循环期间，标准存储空间上的一百万个 InnoDB 表空间可能需要 5 到 8 个小时的处理时间。在一些案例中，InnoDB 可能会在一个崩溃恢复循环后确定它需要额外清理，因此，它会开始另一个崩溃恢复循环，这会使恢复时间延长。请记住，崩溃恢复循环也会引起回滚事务、修复损坏页面和除表空间信息处理以外的其他操作。

因为 `innodb_file_per_table` 参数存储在参数组中，所以可通过编辑数据库实例所使用的参数组来更改该参数值，无需重新启动该数据库实例。更改该设置后，例如从 1 (创建独立的表) 改为 0 (使用共享的表空间)，新的 InnoDB 表将添加到共享的表空间，而现有的表继续使用独立的表空间。要将 InnoDB 表移动到共享的表空间，必须使用 `ALTER TABLE` 命令。

## 将多个表空间迁移到共享的表空间
<a name="Appendix.MySQL.CommonDBATasks.MigrateMultiTbs"></a>

您可将 InnoDB 表的元数据从其自己的表空间移至共享的表空间，这将按照 `innodb_file_per_table` 参数设置重建表的元数据。首先连接到 MySQL 数据库实例，然后发出正确的命令，如下所示。有关更多信息，请参阅“[连接到 MySQL 数据库实例](USER_ConnectToInstance.md)”。

```
ALTER TABLE table_name ENGINE = InnoDB, ALGORITHM=COPY; 
```

例如，以下查询为不在共享表空间中的每个 InnoDB 表返回 `ALTER TABLE` 语句。

**对于 MySQL 5.7 数据库实例：**

```
SELECT CONCAT('ALTER TABLE `', 
REPLACE(LEFT(NAME , INSTR((NAME), '/') - 1), '`', '``'), '`.`', 
REPLACE(SUBSTR(NAME FROM INSTR(NAME, '/') + 1), '`', '``'), '` ENGINE=InnoDB, ALGORITHM=COPY;') AS Query 
FROM INFORMATION_SCHEMA.INNODB_SYS_TABLES 
WHERE SPACE <> 0 AND LEFT(NAME, INSTR((NAME), '/') - 1) NOT IN ('mysql','');
```

**对于 MySQL 8.4 和 8.0 数据库实例：**

```
SELECT CONCAT('ALTER TABLE `', 
REPLACE(LEFT(NAME , INSTR((NAME), '/') - 1), '`', '``'), '`.`', 
REPLACE(SUBSTR(NAME FROM INSTR(NAME, '/') + 1), '`', '``'), '` ENGINE=InnoDB, ALGORITHM=COPY;') AS Query 
FROM INFORMATION_SCHEMA.INNODB_TABLES 
WHERE SPACE <> 0 AND LEFT(NAME, INSTR((NAME), '/') - 1) NOT IN ('mysql','');
```

重建一个 MySQL 表以便将该表的元数据移动到共享的表空间，这需要额外的临时存储空间以重建该表，因此，该数据库实例必须有可用的存储空间。在重建期间，该表会被锁定，无法访问查询。对于不经常访问的小型表或普通表，这可能不是问题。对于在大量并发环境中经常访问的大型表或普通表，您可以在只读副本上重建表。

您可以创建只读副本，并将表元数据迁移到只读副本上的共享表空间。虽然 ALTER TABLE 语句会阻止访问只读副本，但源数据库实例不会受到影响。尽管只读副本会在表重建过程中滞后，但源数据库实例将继续生成其二进制日志。由于重建需要额外的存储空间，并且重放日志文件可能会变得很大，因此，您应该创建只读副本，并向其分配大于源数据库实例的存储空间。

要创建只读副本和重建 InnoDB 表以便使用共享表空间，请遵循以下步骤：

1. 确保在源数据库实例上启用备份保留，以便启用二进制日志记录。

1. 使用 AWS 管理控制台或 AWS CLI 创建源数据库实例的只读副本。由于创建只读副本涉及的流程与崩溃恢复一样多，因此，如果存在大量 InnoDB 表空间，创建过程可能会需要一些时间。在只读副本上分配的存储空间应大于源数据库实例上当前使用的空间。

1. 创建只读副本后，使用参数设置 `read_only = 0` 和 `innodb_file_per_table = 0` 创建参数组，然后将该参数组与只读副本关联。

1. 针对您要在副本中迁移的所有表发出以下 SQL 语句。

   ```
   ALTER TABLE name ENGINE = InnoDB
   ```

1. 在只读副本中完成所有 `ALTER TABLE` 语句后，验证只读副本是否连接至源数据库实例，并且两个实例是否同步。

1. 使用控制台或 CLI 将只读副本提升为实例。确保用于新的独立数据库实例的参数组已将 `innodb_file_per_table` 参数设置为 0。更改新的独立数据库实例的名称，并将所有应用程序指向新的独立数据库实例。