14.13.1 在线DDL操作
- 索引操作
- 主键操作
- 列操作
- 生成列操作
- 外键操作
- 表操作
- 表空间操作
- 分区操作
索引操作
下表概述了对索引操作的在线DDL支持情况。星号表示有附加信息、例外情况或依赖条件。有关详细信息,请参阅语法和使用说明。
操作 | 原地执行 | 重建表 | 允许并发DML | 仅修改元数据 |
---|---|---|---|---|
创建或添加二级索引 | 是 | 否 | 是 | 否 |
删除索引 | 是 | 否 | 是 | 是 |
重命名索引 | 是 | 否 | 是 | 是 |
添加全文索引 | 是* | 否* | 否 | 否 |
添加空间索引 | 是 | 否 | 否 | 否 |
更改索引类型 | 是 | 否 | 是 | 是 |
语法和使用说明
- 创建或添加二级索引
CREATE INDEX name ON table (col_list);
ALTER TABLE tbl_name ADD INDEX name (col_list);
在创建索引期间,表仍然可用于读写操作。CREATE INDEX
语句只有在所有正在访问该表的事务都完成后才会结束,这样索引的初始状态就能反映表的最新内容。
对添加二级索引的在线DDL支持意味着,通常可以通过先创建不含二级索引的表,然后在数据加载后再添加二级索引的方式,加快创建和加载表以及相关索引的整体进程。
新创建的二级索引只包含在CREATE INDEX
或ALTER TABLE
语句执行完成时表中已提交的数据。它不包含任何未提交的值、值的旧版本,或已标记为删除但尚未从旧索引中移除的值。
如果在创建二级索引时服务器退出,恢复后MySQL会删除任何部分创建的索引。你必须重新运行ALTER TABLE
或CREATE INDEX
语句。
一些因素会影响此操作的性能、空间使用和语义。有关详细信息,请参阅14.13.6节 “在线DDL的限制”。
2. 删除索引
DROP INDEX name ON table;
ALTER TABLE tbl_name DROP INDEX name;
在删除索引期间,表仍然可用于读写操作。DROP INDEX
语句只有在所有正在访问该表的事务都完成后才会结束,这样索引的初始状态就能反映表的最新内容。
3. 重命名索引
ALTER TABLE tbl_name RENAME INDEX old_index_name TO new_index_name, ALGORITHM=INPLACE, LOCK=NONE;
- 添加全文索引
CREATE FULLTEXT INDEX name ON table(column);
如果没有用户定义的FTS_DOC_ID
列,添加第一个全文索引会重建表。后续添加全文索引则可能无需重建表。
5. 添加空间索引
CREATE TABLE geom (g GEOMETRY NOT NULL);
ALTER TABLE geom ADD SPATIAL INDEX(g), ALGORITHM=INPLACE, LOCK=SHARED;
- 更改索引类型(使用 {BTREE | HASH})
ALTER TABLE tbl_name DROP INDEX i1, ADD INDEX i1(key_part,...) USING BTREE, ALGORITHM=INPLACE;
主键操作
下表概述了对主键操作的在线DDL支持情况。星号表示有附加信息、例外情况或依赖条件。请参阅语法和使用说明。
操作 | 原地执行 | 重建表 | 允许并发DML | 仅修改元数据 |
---|---|---|---|---|
添加主键 | 是* | 是* | 是 | 否 |
删除主键 | 否 | 是 | 否 | 否 |
删除主键并添加另一个主键 | 是 | 是 | 是 | 否 |
语法和使用说明
- 添加主键
ALTER TABLE tbl_name ADD PRIMARY KEY (column), ALGORITHM=INPLACE, LOCK=NONE;
原地重建表。数据会进行大量重组,这是一项开销较大的操作。如果必须将列转换为NOT NULL
,在某些条件下不允许使用ALGORITHM=INPLACE
。
重组聚簇索引总是需要复制表数据。因此,最好在创建表时就定义主键,而不是之后再使用ALTER TABLE... ADD PRIMARY KEY
语句。
当你创建UNIQUE
或PRIMARY KEY
索引时,MySQL必须执行一些额外的工作。对于UNIQUE
索引,MySQL会检查表中该键是否存在重复值。对于PRIMARY KEY
索引,MySQL还会检查PRIMARY KEY
列中是否有NULL
值。
当你使用ALGORITHM=COPY
子句添加主键时,MySQL会将相关列中的NULL
值转换为默认值:数字类型转换为0
,基于字符的列和BLOB
类型转换为空字符串,DATETIME
类型转换为0000-00-00 00:00:00
。这是一种非标准行为,Oracle建议你不要依赖它。只有当SQL_MODE
设置中包含strict_trans_tables
或strict_all_tables
标志时,才允许使用ALGORITHM=INPLACE
添加主键;当SQL_MODE
设置为严格模式时,允许使用ALGORITHM=INPLACE
,但如果请求的主键列中包含NULL
值,语句仍然可能失败。ALGORITHM=INPLACE
的行为更符合标准。
如果你创建的表没有主键,InnoDB会为你选择一个,可能是在NOT NULL
列上定义的第一个UNIQUE
键,或者是系统生成的键。为了避免不确定性以及额外隐藏列可能带来的空间需求,应在CREATE TABLE
语句中指定PRIMARY KEY
子句。
MySQL通过将原始表中的现有数据复制到具有所需索引结构的临时表中来创建新的聚簇索引。一旦数据完全复制到临时表中,原始表会被重命名为一个不同的临时表名。包含新聚簇索引的临时表会被重命名为原始表的名称,而原始表则会从数据库中删除。
应用于二级索引操作的在线性能增强不适用于主键索引。InnoDB表的行存储在基于主键组织的聚簇索引中,形成了一些数据库系统所称的 “索引组织表”。由于表结构与主键紧密相关,重新定义主键仍然需要复制数据。
当对主键的操作使用ALGORITHM=INPLACE
时,即使仍然需要复制数据,它也比使用ALGORITHM=COPY
更高效,原因如下:
ALGORITHM=INPLACE
不需要撤销日志记录或相关的重做日志记录。这些操作会增加使用ALGORITHM=COPY
的DDL语句的开销。- 二级索引条目是预排序的,因此可以按顺序加载。
- 不使用更改缓冲区,因为不会对二级索引进行随机访问插入。
如果在创建新的聚簇索引时服务器退出,不会丢失数据,但你必须使用该过程中存在的临时表完成恢复过程。由于在大型表上重新创建聚簇索引或重新定义主键的情况很少见,并且在该操作期间遇到系统崩溃的情况也很少,因此本手册不提供有关从这种情况中恢复的信息。
2. 删除主键
ALTER TABLE tbl_name DROP PRIMARY KEY, ALGORITHM=COPY;
只有ALGORITHM=COPY
支持在同一个ALTER TABLE
语句中删除主键而不添加新的主键。
3. 删除主键并添加另一个主键
ALTER TABLE tbl_name DROP PRIMARY KEY, ADD PRIMARY KEY (column), ALGORITHM=INPLACE, LOCK=NONE;
数据会进行大量重组,这是一项开销较大的操作。
列操作
下表概述了对列操作的在线DDL支持情况。星号表示有附加信息、例外情况或依赖条件。有关详细信息,请参阅语法和使用说明。
操作 | 原地执行 | 重建表 | 允许并发DML | 仅修改元数据 |
---|---|---|---|---|
添加列 | 是 | 是 | 是* | 否 |
删除列 | 是 | 是 | 是 | 否 |
重命名列 | 是 | 否 | 是* | 是 |
重新排序列 | 是 | 是 | 是 | 否 |
设置列默认值 | 是 | 否 | 是 | 是 |
更改列数据类型 | 否 | 是 | 否 | 否 |
扩展VARCHAR列大小 | 是 | 否 | 是 | 是 |
删除列默认值 | 是 | 否 | 是 | 是 |
更改自动递增的值 | 是 | 否 | 是 | 否* |
将列设为NULL | 是 | 是* | 是 | 否 |
将列设为NOT NULL | 是* | 是* | 是 | 否 |
修改ENUM或SET列的定义 | 是 | 否 | 是 | 是 |
语法和使用说明
- 添加列
ALTER TABLE tbl_name ADD COLUMN column_name column_definition, ALGORITHM=INPLACE, LOCK=NONE;
添加自动递增列时不允许并发DML。数据会进行大量重组,这是一项开销较大的操作。至少需要ALGORITHM=INPLACE, LOCK=SHARED
。
2. 删除列
ALTER TABLE tbl_name DROP COLUMN column_name, ALGORITHM=INPLACE, LOCK=NONE;
数据会进行大量重组,这是一项开销较大的操作。
3. 重命名列
ALTER TABLE tbl CHANGE old_col_name new_col_name data_type, ALGORITHM=INPLACE, LOCK=NONE;
为了允许并发DML,应保持相同的数据类型,只更改列名。
当你保持相同的数据类型和[NOT] NULL
属性,仅更改列名时,该操作始终可以在线执行。
你还可以重命名作为外键约束一部分的列。外键定义会自动更新以使用新的列名。重命名参与外键的列仅在ALGORITHM=INPLACE
时有效。如果你使用ALGORITHM=COPY
子句,或者某些其他条件导致操作使用ALGORITHM=COPY
,ALTER TABLE
语句将失败。
不支持使用ALGORITHM=INPLACE
重命名生成列。
4. 重新排序列
要重新排序列,可在CHANGE
或MODIFY
操作中使用FIRST
或AFTER
。
ALTER TABLE tbl_name MODIFY COLUMN col_name column_definition FIRST, ALGORITHM=INPLACE, LOCK=NONE;
数据会进行大量重组,这是一项开销较大的操作。
5. 更改列数据类型
ALTER TABLE tbl_name CHANGE c1 c1 BIGINT, ALGORITHM=COPY;
仅支持使用ALGORITHM=COPY
更改列数据类型。
6. 扩展VARCHAR列大小
ALTER TABLE tbl_name CHANGE COLUMN c1 c1 VARCHAR(255), ALGORITHM=INPLACE, LOCK=NONE;
VARCHAR
列所需的长度字节数必须保持不变。对于大小为0到255字节的VARCHAR
列,需要一个长度字节来编码该值。对于大小为256字节或更大的VARCHAR
列,需要两个长度字节。因此,原地ALTER TABLE
仅支持将VARCHAR
列大小从0增加到255字节,或者从256字节增加到更大的大小。原地ALTER TABLE
不支持将VARCHAR
列的大小从小于256字节增加到等于或大于256字节。在这种情况下,所需的长度字节数从1变为2,这仅支持通过表复制(ALGORITHM=COPY
)实现。例如,尝试使用原地ALTER TABLE
将单字节字符集的VARCHAR
列大小从VARCHAR(255)
更改为VARCHAR(256)
会返回以下错误:
ALTER TABLE tbl_name ALGORITHM=INPLACE, CHANGE COLUMN c1 c1 VARCHAR(256);
ERROR 0A000: ALGORITHM=INPLACE is not supported. Reason: Cannot change
column type INPLACE. Try ALGORITHM=COPY.
注意:VARCHAR
列的字节长度取决于字符集的字节长度。
不支持使用原地ALTER TABLE
减小VARCHAR
列的大小。减小VARCHAR
列的大小需要进行表复制(ALGORITHM=COPY
)。
7. 设置列默认值
ALTER TABLE tbl_name ALTER COLUMN col SET DEFAULT literal, ALGORITHM=INPLACE, LOCK=NONE;
仅修改表元数据。默认列值存储在表的.frm
文件中,而不是InnoDB数据字典中。
8. 删除列默认值
ALTER TABLE tbl ALTER COLUMN col DROP DEFAULT, ALGORITHM=INPLACE, LOCK=NONE;
- 更改自动递增的值
ALTER TABLE table AUTO_INCREMENT=next_value, ALGORITHM=INPLACE, LOCK=NONE;
修改存储在内存中的值,而不是数据文件中的值。
在使用复制或分片的分布式系统中,有时你需要将表的自动递增计数器重置为特定值。插入到表中的下一行将使用指定的值作为其自动递增列的值。你也可能在数据仓库环境中使用此技术,在该环境中,你会定期清空所有表并重新加载它们,并从1重新开始自动递增序列。
10. 将列设为NULL
ALTER TABLE tbl_name MODIFY COLUMN column_name data_type NULL, ALGORITHM=INPLACE, LOCK=NONE;
原地重建表。数据会进行大量重组,这是一项开销较大的操作。
11. 将列设为NOT NULL
ALTER TABLE tbl_name MODIFY COLUMN column_name data_type NOT NULL, ALGORITHM=INPLACE, LOCK=NONE;
原地重建表。操作成功需要STRICT_ALL_TABLES
或STRICT_TRANS_TABLES
SQL模式。如果列中包含NULL
值,操作将失败。服务器禁止对可能导致引用完整性丢失的外键列进行更改。请参阅13.1.8节 “ALTER TABLE语句”。数据会进行大量重组,这是一项开销较大的操作。
12. 修改ENUM或SET列的定义
CREATE TABLE t1 (c1 ENUM('a', 'b', 'c'));
ALTER TABLE t1 MODIFY COLUMN c1 ENUM('a', 'b', 'c', 'd'), ALGORITHM=INPLACE, LOCK=NONE;
只要数据类型的存储大小不变,通过在有效成员值列表末尾添加新的枚举或集合成员来修改ENUM
或SET
列的定义可以原地执行。例如,向具有8个成员的SET
列添加一个成员会将每个值所需的存储从1字节更改为2字节;这需要进行表复制。在列表中间添加成员会导致对现有成员进行重新编号,这也需要进行表复制。
生成列操作
下表概述了对生成列操作的在线DDL支持情况。有关详细信息,请参阅语法和使用说明。
操作 | 原地执行 | 重建表 | 允许并发DML | 仅修改元数据 |
---|---|---|---|---|
添加存储列 | 否 | 是 | 否 | 否 |
修改存储列顺序 | 否 | 是 | 否 | 否 |
删除存储列 | 是 | 是 | 是 | 否 |
添加虚拟列 | 是 | 否 | 是 | 是 |
修改虚拟列顺序 | 否 | 是 | 否 | 否 |
删除虚拟列 | 是 | 否 | 是 | 是 |
语法和使用说明
- 添加存储列
ALTER TABLE t1 ADD COLUMN (c2 INT GENERATED ALWAYS AS (c1 + 1) STORED), ALGORITHM=COPY;
对于存储列,ADD COLUMN
不是原地操作(不使用临时表完成),因为表达式必须由服务器进行计算。
2. 修改存储列顺序
ALTER TABLE t1 MODIFY COLUMN c2 INT GENERATED ALWAYS AS (c1 + 1) STORED FIRST, ALGORITHM=COPY;
原地重建表。
3. 删除存储列
ALTER TABLE t1 DROP COLUMN c2, ALGORITHM=INPLACE, LOCK=NONE;
原地重建表。
4. 添加虚拟列
ALTER TABLE t1 ADD COLUMN (c2 INT GENERATED ALWAYS AS (c1 + 1) VIRTUAL), ALGORITHM=INPLACE, LOCK=NONE;
对于非分区表,添加虚拟列是一个原地操作。然而,添加虚拟列不能与其他ALTER TABLE
操作结合进行。
对于分区表,添加虚拟列不是一个原地操作。
5. 修改虚拟列顺序
ALTER TABLE t1 MODIFY COLUMN c2 INT GENERATED ALWAYS AS (c1 + 1) VIRTUAL FIRST, ALGORITHM=COPY;
- 删除虚拟列
ALTER TABLE t1 DROP COLUMN c2, ALGORITHM=INPLACE, LOCK=NONE;
对于非分区表,删除虚拟列是一个原地操作。然而,删除虚拟列不能与其他ALTER TABLE
操作结合进行。
对于分区表,删除虚拟列不是一个原地操作。
外键操作
下表概述了对外键操作的在线DDL支持情况。星号表示有附加信息、例外情况或依赖条件。有关详细信息,请参阅语法和使用说明。
操作 | 原地执行 | 重建表 | 允许并发DML | 仅修改元数据 |
---|---|---|---|---|
添加外键约束 | 是* | 否 | 是 | 是 |
删除外键约束 | 是 | 否 | 是 | 是 |
语法和使用说明
- 添加外键约束
当foreign_key_checks
禁用时,支持INPLACE
算法。否则,仅支持COPY
算法。
ALTER TABLE tbl1 ADD CONSTRAINT fk_name FOREIGN KEY index (col1)REFERENCES tbl2(col2) referential_actions;
- 删除外键约束
ALTER TABLE tbl DROP FOREIGN KEY fk_name;
无论foreign_key_checks
选项是启用还是禁用,都可以在线删除外键。
如果你不知道特定表上的外键约束名称,可以执行以下语句,并在每个外键的CONSTRAINT
子句中查找约束名称:
SHOW CREATE TABLE table\G
或者,查询Information Schema
的TABLE_CONSTRAINTS
表,并使用CONSTRAINT_NAME
和CONSTRAINT_TYPE
列来识别外键名称。
你也可以在单个语句中删除外键及其关联的索引:
ALTER TABLE table DROP FOREIGN KEY constraint, DROP INDEX index;
注意:如果正在更改的表中已经存在外键(即它是一个包含FOREIGN KEY...REFERENCE
子句的子表),即使是那些不直接涉及外键列的在线DDL操作,也会有额外的限制:
- 如果父表的更改通过使用
CASCADE
或SET NULL
参数的ON UPDATE
或ON DELETE
子句导致子表发生相关更改,对子表的ALTER TABLE
操作可能会等待另一个事务提交。 - 同样,如果一个表是外键关系中的父表,即使它不包含任何
FOREIGN KEY
子句,如果INSERT
、UPDATE
或DELETE
语句导致子表中发生ON UPDATE
或ON DELETE
操作,它也可能会等待ALTER TABLE
操作完成。
表操作
下表概述了对表操作的在线DDL支持情况。星号表示有附加信息、例外情况或依赖条件。有关详细信息,请参阅语法和使用说明。
操作 | 原地执行 | 重建表 | 允许并发DML | 仅修改元数据 |
---|---|---|---|---|
更改行格式 | 是 | 是 | 是 | 否 |
更改键块大小 | 是 | 是 | 是 | 否 |
设置持久表统计信息 | 是 | 否 | 是 | 是 |
指定字符集 | 是 | 是* | 是 | 否 |
转换字符集 | 否 | 是* | 否 | 否 |
优化表 | 是* | 是 | 是 | 否 |
使用FORCE选项重建表 | 是* | 是 | 是 | 否 |
执行空重建 | 是* | 是 | 是 | 否 |
重命名表 | 是 | 否 | 是 | 是 |
语法和使用说明
- 更改行格式
ALTER TABLE tbl_name ROW_FORMAT = row_format, ALGORITHM=INPLACE, LOCK=NONE;
数据会进行大量重组,这是一项开销较大的操作。
有关ROW_FORMAT
选项的更多信息,请参阅表选项。
2. 更改键块大小
ALTER TABLE tbl_name KEY_BLOCK_SIZE = value, ALGORITHM=INPLACE, LOCK=NONE;
数据会进行大量重组,这是一项开销较大的操作。
有关KEY_BLOCK_SIZE
选项的更多信息,请参阅表选项。
3. 设置持久表统计信息选项
ALTER TABLE tbl_name STATS_PERSISTENT=0, STATS_SAMPLE_PAGES=20, STATS_AUTO_RECALC=1, ALGORITHM=INPLACE, LOCK=NONE;
仅修改表元数据。
持久统计信息包括STATS_PERSISTENT
、STATS_AUTO_RECALC
和STATS_SAMPLE_PAGES
。有关更多信息,请参阅14.8.11.1节 “配置持久优化器统计信息参数”。
4. 指定字符集
ALTER TABLE tbl_name CHARACTER SET = charset_name, ALGORITHM=INPLACE, LOCK=NONE;
如果新的字符编码不同,则会重建表。
5. 转换字符集
ALTER TABLE tbl_name CONVERT TO CHARACTER SET charset_name, ALGORITHM=COPY;
如果新的字符编码不同,则会重建表。
6. 优化表
OPTIMIZE TABLE tbl_name;
对于包含全文索引的表,不支持原地操作。该操作使用INPLACE
算法,但不允许使用ALGORITHM
和LOCK
语法。
7. 使用FORCE选项重建表
ALTER TABLE tbl_name FORCE, ALGORITHM=INPLACE, LOCK=NONE;
从MySQL 5.6.17开始使用ALGORITHM=INPLACE
。对于包含全文索引的表,不支持ALGORITHM=INPLACE
。
8. 执行空重建
ALTER TABLE tbl_name ENGINE=InnoDB, ALGORITHM=INPLACE, LOCK=NONE;
从MySQL 5.6.17开始使用ALGORITHM=INPLACE
。对于包含全文索引的表,不支持ALGORITHM=INPLACE
。
9. 重命名表
ALTER TABLE old_tbl_name RENAME TO new_tbl_name, ALGORITHM=INPLACE, LOCK=NONE;
MySQL会重命名与表tbl_name
对应的文件,而不进行复制。(你也可以使用RENAME TABLE
语句来重命名表。请参阅13.1.33节 “RENAME TABLE语句”。)专门为重命名的表授予的权限不会迁移到新名称。必须手动更改这些权限。
表空间操作
下表概述了对表空间操作的在线DDL支持情况。有关详细信息,请参阅语法和使用说明。
操作 | 原地执行 | 重建表 | 允许并发DML | 仅修改元数据 |
---|---|---|---|---|
启用或禁用文件表空间加密 | 否 | 是 | 否 | 否 |
语法和使用说明
- 启用或禁用文件表空间加密
ALTER TABLE tbl_name ENCRYPTION='Y', ALGORITHM=COPY;
仅支持对文件表空间进行加密。有关相关信息,请参阅14.14节 “InnoDB静态数据加密”。
分区操作
除了大多数ALTER TABLE
分区子句外,分区InnoDB表的在线DDL操作遵循与常规InnoDB表相同的规则。
大多数ALTER TABLE
分区子句不会像常规非分区InnoDB表那样通过相同的内部在线DDL API。因此,对ALTER TABLE
分区子句的在线支持情况各不相同。
下表显示了每个ALTER TABLE
分区语句的在线状态。无论使用哪种在线DDL API,MySQL都会尽可能减少数据复制和锁定。
使用ALGORITHM=COPY
或仅允许“ALGORITHM=DEFAULT, LOCK=DEFAULT”
的ALTER TABLE
分区选项,会使用COPY
算法对表进行重新分区。换句话说,会使用新的分区方案创建一个新的分区表。新创建的表包括ALTER TABLE
语句应用的任何更改,并且表数据会被复制到新的表结构中。
分区子句 | 原地执行 | 允许DML | 备注 |
---|---|---|---|
PARTITION BY | 否 | 否 | 允许`ALGORITHM=COPY, LOCK={DEFAULT |
ADD PARTITION | 否 | 否 | 仅允许ALGORITHM=DEFAULT, LOCK=DEFAULT 。对于按RANGE 或LIST 分区的表,不复制现有数据。对于按HASH 或LIST 分区的表,允许并发查询。MySQL在持有共享锁的同时复制数据。 |
DROP PARTITION | 否 | 否 | 仅允许ALGORITHM=DEFAULT, LOCK=DEFAULT 。对于按RANGE 或LIST 分区的表,不复制现有数据。 |
DISCARD PARTITION | 否 | 否 | 仅允许ALGORITHM=DEFAULT, LOCK=DEFAULT |
IMPORT PARTITION | 否 | 否 | 仅允许ALGORITHM=DEFAULT, LOCK=DEFAULT |
TRUNCATE PARTITION | 是 | 是 | 不复制现有数据。它只是删除行;它不会更改表本身或其任何分区的定义。 |
COALESCE PARTITION | 否 | 否 | 仅允许ALGORITHM=DEFAULT, LOCK=DEFAULT 。对于按HASH 或LIST 分区的表,允许并发查询,因为MySQL在持有共享锁的同时复制数据。 |
REORGANIZE PARTITION | 否 | 否 | 仅允许ALGORITHM=DEFAULT, LOCK=DEFAULT 。对于按LINEAR HASH 或LIST 分区的表,允许并发查询。MySQL在持有共享元数据锁的同时从受影响的分区复制数据。 |
EXCHANGE PARTITION | 是 | 是 | |
ANALYZE PARTITION | 是 | 是 | |
CHECK PARTITION | 是 | 是 | |
OPTIMIZE PARTITION | 否 | 否 | ALGORITHM 和LOCK 子句将被忽略。重建整个表。请参阅22.3.4节 “分区的维护”。 |
REBUILD PARTITION | 否 | 否 | 仅允许ALGORITHM=DEFAULT, LOCK=DEFAULT 。对于按LINEAR HASH 或LIST 分区的表,允许并发查询。MySQL在持有共享元数据锁的同时从受影响的分区复制数据。 |
REPAIR PARTITION | 是 | 是 | |
REMOVE PARTITIONING | 否 | 否 | 允许`ALGORITHM=COPY, LOCK={DEFAULT |
分区子句 | 原地执行 | 允许DML | 备注 |
对分区表进行的非分区在线ALTER TABLE
操作遵循与常规表相同的规则。然而,ALTER TABLE
会对每个表分区执行在线操作,由于要对多个分区进行操作,这会导致对系统资源的需求增加。
有关ALTER TABLE
分区子句的更多信息,请参阅分区选项和13.1.8.1节 “ALTER TABLE分区操作”。有关分区的一般信息,请参阅第22章 “分区”。
14.13.2 在线DDL的性能和并发性
在线DDL在多个方面改进了MySQL的操作:
- 访问表的应用程序响应更加灵敏,因为在DDL操作进行时,对表的查询和DML操作可以继续进行。减少了对MySQL服务器资源的锁定和等待,即使对于那些不涉及DDL操作的业务,也提高了系统的可扩展性。
- 原地操作避免了与表复制方法相关的磁盘I/O和CPU周期,从而最大限度地减少了数据库的整体负载。减少负载有助于在DDL操作期间保持良好的性能和高吞吐量。
- 原地操作比表复制操作读取到缓冲池中的数据更少,这减少了从内存中清除频繁访问数据的情况。频繁访问的数据被清除可能会在DDL操作后导致暂时的性能下降。
LOCK子句
默认情况下,MySQL在DDL操作期间尽可能少地使用锁定。如果需要,可以指定LOCK
子句来实施更严格的锁定。如果LOCK
子句指定的锁定级别比特定DDL操作允许的级别更宽松,语句将因错误而失败。LOCK
子句按从宽松到严格的顺序描述如下:
- LOCK=NONE:允许并发查询和DML。例如,对于涉及客户注册或购买的表,使用此子句可以避免在长时间的DDL操作期间使表不可用。
- LOCK=SHARED:允许并发查询,但阻止DML。例如,在数据仓库表上使用此子句,在这种情况下,你可以将数据加载操作延迟到DDL操作完成,但查询不能长时间延迟。
- LOCK=DEFAULT:允许尽可能多的并发(并发查询、DML或两者皆可)。省略
LOCK
子句与指定LOCK=DEFAULT
相同。当你知道DDL语句的默认锁定级别不会对表的可用性造成问题时,使用此子句。 - LOCK=EXCLUSIVE:阻止并发查询和DML。如果首要考虑的是在尽可能短的时间内完成DDL操作,并且不需要并发查询和DML访问,则使用此子句。如果服务器应该处于空闲状态,你也可以使用此子句,以避免意外的表访问。
在线DDL和元数据锁
在线DDL操作可以分为三个阶段:
- 阶段1:初始化:在初始化阶段,服务器会考虑存储引擎的功能、语句中指定的操作以及用户指定的
ALGORITHM
和LOCK
选项,来确定操作期间允许的并发程度。在此阶段,会获取一个共享可升级的元数据锁,以保护当前的表定义。 - 阶段2:执行:在此阶段,语句将被准备和执行。元数据锁是否升级为排他锁取决于在初始化阶段评估的因素。如果需要排他元数据锁,它只会在语句准备期间短暂获取。
- 阶段3:提交表定义:在提交表定义阶段,元数据锁将升级为排他锁,以清除旧的表定义并提交新的表定义。一旦获得排他锁,其持续时间很短。
由于上述排他元数据锁的要求,在线DDL操作可能需要等待持有表元数据锁的并发事务提交或回滚。在DDL操作之前或期间启动的事务可能会持有正在更改的表的元数据锁。在长运行或非活动事务的情况下,在线DDL操作可能会因等待排他元数据锁而超时。此外,在线DDL操作请求的挂起排他元数据锁会阻止表上的后续事务。
以下示例演示了在线DDL操作等待排他元数据锁的情况,以及挂起的元数据锁如何阻止表上的后续事务:
- 会话1:
mysql> CREATE TABLE t1 (c1 INT) ENGINE=InnoDB;
mysql> START TRANSACTION;
mysql> SELECT * FROM t1;
会话1的SELECT
语句对表t1
获取了一个共享元数据锁。
- 会话2:
mysql> ALTER TABLE t1 ADD COLUMN x INT, ALGORITHM=INPLACE, LOCK=NONE;
会话2中的在线DDL操作需要对表t1
获取排他元数据锁以提交表定义更改,因此它必须等待会话1中的事务提交或回滚。
- 会话3:
mysql> SELECT * FROM t1;
会话3中发出的SELECT
语句会被阻塞,等待会话2中的ALTER TABLE
操作请求的排他元数据锁被授予。
你可以使用SHOW FULL PROCESSLIST
来确定事务是否在等待元数据锁。
mysql> SHOW FULL PROCESSLIST\G
...
*************************** 2. row ***************************Id: 5User: rootHost: localhostdb: test
Command: QueryTime: 44State: Waiting for table metadata lockInfo: ALTER TABLE t1 ADD COLUMN x INT, ALGORITHM=INPLACE, LOCK=NONE
...
*************************** 4. row ***************************Id: 7User: rootHost: localhostdb: test
Command: QueryTime: 5State: Waiting for table metadata lockInfo: SELECT * FROM t1
4 rows in set (0.00 sec)
元数据锁信息也会通过Performance Schema
的metadata_locks
表公开,该表提供了有关会话之间元数据锁依赖关系、会话正在等待的元数据锁以及当前持有元数据锁的会话的信息。有关更多信息,请参阅25.12.12.1节 “metadata_locks### 表”。
在线DDL性能
DDL操作的性能在很大程度上取决于该操作是原地执行还是需要重建表。
为了评估DDL操作的相对性能,你可以比较使用ALGORITHM = INPLACE
和ALGORITHM = COPY
的结果。或者,你也可以比较禁用和启用old_alter_table
时的结果。
对于修改表数据的DDL操作,你可以通过查看命令完成后显示的 “受影响的行数” 值来确定该操作是原地执行还是进行了表复制。例如:
- 更改列的默认值(速度快,不影响表数据):
Query OK, 0 rows affected (0.07 sec)
- 添加索引(需要时间,但 “受影响的行数” 为 0 表明表未被复制):
Query OK, 0 rows affected (21.42 sec)
- 更改列的数据类型(需要大量时间,并且需要重建表的所有行):
Query OK, 1671168 rows affected (1 min 35.54 sec)
在对大型表执行DDL操作之前,可按以下步骤检查该操作是快还是慢:
- 克隆表结构。
- 用少量数据填充克隆表。
- 在克隆表上运行DDL操作。
- 检查 “受影响的行数” 是否为零。非零值意味着该操作会复制表数据,这可能需要进行特殊规划。例如,你可以在计划的停机期间执行DDL操作,或者在每个副本服务器上依次执行。
注意:为了更好地了解与DDL操作相关的MySQL处理过程,你可以在DDL操作前后检查与InnoDB相关的Performance Schema
和INFORMATION_SCHEMA
表,以查看物理读取、写入、内存分配等的数量。
Performance Schema
阶段事件可用于监控ALTER TABLE
的进度。请参阅14.17.1节 “使用Performance Schema监控InnoDB表的ALTER TABLE进度”。
由于记录并发DML操作所做的更改,然后在最后应用这些更改会涉及一些处理工作,因此在线DDL操作的总体时间可能比阻止其他会话访问表的表复制机制更长。不过,这种原始性能的降低可以通过使用该表的应用程序获得更好的响应能力来平衡。在评估更改表结构的技术时,应根据网页加载时间等因素考虑最终用户对性能的感知。
14.13.3 在线DDL的空间要求
在线DDL操作有以下空间要求:
临时日志文件
当在线DDL操作创建索引或修改表时,临时日志文件会记录并发DML。临时日志文件会根据innodb_sort_buffer_size
的值按需扩展,最大不超过innodb_online_alter_log_max_size
指定的值。如果操作耗时较长,并且并发DML对表的修改非常大,导致临时日志文件的大小超过innodb_online_alter_log_max_size
的值,在线DDL操作将以DB_ONLINE_LOG_TOO_BIG
错误失败,并且未提交的并发DML操作将被回滚。较大的innodb_online_alter_log_max_size
设置允许在在线DDL操作期间进行更多的DML,但也会延长DDL操作结束时锁定表以应用记录的DML的时间。
innodb_sort_buffer_size
变量还定义了临时日志文件读取缓冲区和写入缓冲区的大小。
临时排序文件
重建表的在线DDL操作在创建索引期间会将临时排序文件写入MySQL临时目录(Unix系统上为$TMPDIR
,Windows系统上为%TEMP%
,或者由--tmpdir
指定的目录)。临时排序文件不会创建在包含原始表的目录中。每个临时排序文件的大小足以容纳一列数据,并且当数据合并到最终表或索引中时,每个排序文件都会被删除。涉及临时排序文件的操作可能需要的临时空间等于表中的数据量加上索引的大小。如果在线DDL操作使用了数据目录所在文件系统上的所有可用磁盘空间,将报告错误。
如果MySQL临时目录不足以容纳排序文件,可以将tmpdir
设置为不同的目录。或者,使用innodb_tmpdir
为在线DDL操作定义一个单独的临时目录。该选项在MySQL 5.7.11中引入,用于帮助避免因大型临时排序文件而导致的临时目录溢出。
中间表文件
一些重建表的在线DDL操作会在与原始表相同的目录中创建一个临时中间表文件。中间表文件可能需要的空间等于原始表的大小。中间表文件名以#sql - ib
前缀开头,并且仅在在线DDL操作期间短暂出现。
innodb_tmpdir
选项不适用于中间表文件。
14.13.4 使用在线DDL简化DDL语句
在引入在线DDL之前,将许多DDL操作组合到一个ALTER TABLE
语句中是常见的做法。由于每个ALTER TABLE
语句都涉及复制和重建表,因此一次性对同一个表进行多个更改会更高效,因为这些更改可以通过一次表重建操作完成。缺点是涉及DDL操作的SQL代码更难维护,并且在不同脚本中重用也更困难。如果每次的具体更改都不同,你可能需要为每个略有不同的场景构造一个新的复杂ALTER TABLE
语句。
对于可以原地执行的DDL操作,你可以将它们拆分为单独的ALTER TABLE
语句,以便于脚本编写和维护,而不会牺牲效率。例如,你可以将一个复杂的语句,如:
ALTER TABLE t1 ADD INDEX i1(c1), ADD UNIQUE INDEX i2(c2),CHANGE c4_old_name c4_new_name INTEGER UNSIGNED;
拆分为更简单的部分,这些部分可以独立测试和执行,例如:
ALTER TABLE t1 ADD INDEX i1(c1);
ALTER TABLE t1 ADD UNIQUE INDEX i2(c2);
ALTER TABLE t1 CHANGE c4_old_name c4_new_name INTEGER UNSIGNED NOT NULL;
不过,你可能仍然会对以下情况使用多部分的ALTER TABLE
语句:
- 必须按特定顺序执行的操作:例如,先创建一个索引,然后创建一个使用该索引的外键约束。
- 使用相同特定LOCK子句的操作:你希望这些操作作为一个组要么全部成功,要么全部失败。
- 无法原地执行的操作:即仍然使用表复制方法的操作。
- 指定ALGORITHM = COPY或old_alter_table = 1的操作:在特殊场景下,为了实现精确的向后兼容性,可能需要强制使用表复制行为。
14.13.5 在线DDL的失败条件
在线DDL操作失败通常是由以下条件之一导致的:
- ALGORITHM子句指定的算法不兼容:
ALGORITHM
子句指定的算法与特定类型的DDL操作或存储引擎不兼容。 - LOCK子句指定的锁定级别不兼容:
LOCK
子句指定的低锁定级别(SHARED
或NONE
)与特定类型的DDL操作不兼容。 - 等待排他锁超时:在DDL操作的初始和最终阶段可能需要短暂的表排他锁,如果等待该锁时发生超时,操作会失败。
- 临时目录磁盘空间不足:在创建索引时,MySQL在磁盘上写入临时排序文件时,
tmpdir
或innodb_tmpdir
文件系统的磁盘空间耗尽。有关更多信息,请参阅14.13.3节 “在线DDL的空间要求”。 - 临时在线日志过大:操作耗时较长,并且并发DML对表的修改非常大,导致临时在线日志的大小超过
innodb_online_alter_log_max_size
配置选项的值。这种情况会导致DB_ONLINE_LOG_TOO_BIG
错误。 - 并发DML与新表定义冲突:并发DML对表所做的更改在原始表定义下是允许的,但在新表定义下不允许。只有在最后,当MySQL尝试应用并发DML语句的所有更改时,操作才会失败。例如,在创建唯一索引时,你可能会向列中插入重复值;或者在创建主键索引时,向列中插入
NULL
值。并发DML所做的更改优先,ALTER TABLE
操作实际上会被回滚。
14.13.6 在线DDL的限制
以下限制适用于在线DDL操作:
- 临时表创建索引需复制表:在临时表上创建索引时,会复制表。
- 存在特定约束时不允许LOCK = NONE:如果表上存在
ON...CASCADE
或ON...SET NULL
约束,则不允许使用ALTER TABLE
子句LOCK = NONE
。 - 等待元数据锁事务:在在线DDL操作完成之前,必须等待持有表元数据锁的事务提交或回滚。在线DDL操作在执行阶段可能需要短暂的表排他元数据锁,并且在操作的最后阶段更新表定义时总是需要排他元数据锁。因此,持有表元数据锁的事务可能会导致在线DDL操作阻塞。持有表元数据锁的事务可能在在线DDL操作之前或期间启动。长时间运行或非活动的持有表元数据锁的事务可能会导致在线DDL操作超时。
- 外键关系中的操作问题:对外键关系中的表执行在线DDL操作时,不会等待外键关系中另一个表上执行的事务提交或回滚。该事务会对其正在更新的表持有排他元数据锁,并对与外键相关的表持有共享元数据锁(用于外键检查)。共享元数据锁允许在线DDL操作继续进行,但会在操作的最后阶段阻塞该操作,因为更新表定义需要排他元数据锁。这种情况可能会导致死锁,因为其他事务会等待在线DDL操作完成。
- 应用DML日志时可能出现重复键错误:在运行在线DDL操作时,执行
ALTER TABLE
语句的线程会应用在同一表上从其他连接线程并发运行的DML操作的在线日志。在应用DML操作时,即使重复条目只是临时的,并且会被在线日志中的后续条目恢复,也有可能遇到重复键条目错误(ERROR 1062 (23000): Duplicate entry
)。这类似于InnoDB中的外键约束检查,其中约束必须在事务期间保持有效。 - OPTIMIZE TABLE操作:
OPTIMIZE TABLE
对InnoDB表的操作会映射为ALTER TABLE
操作,以重建表、更新索引统计信息并释放聚簇索引中未使用的空间。由于键是按照它们在主键中出现的顺序插入的,因此二级索引的创建效率不高。随着对重建常规和分区InnoDB表的在线DDL支持的添加,OPTIMIZE TABLE
也得到了支持。 - 旧表不支持ALGORITHM = INPLACE:在MySQL 5.6之前创建的包含时态列(
DATE
、DATETIME
或TIMESTAMP
)且未使用ALGORITHM = COPY
重建的表不支持ALGORITHM = INPLACE
。在这种情况下,ALTER TABLE...ALGORITHM = INPLACE
操作会返回以下错误:
ERROR 1846 (0A000): ALGORITHM=INPLACE is not supported.
Reason: Cannot change column type INPLACE. Try ALGORITHM=COPY.
以下限制通常适用于涉及重建大型表的在线DDL操作:
- 无法暂停或限制资源使用:没有机制可以暂停在线DDL操作,也无法限制其I/O或CPU使用。
- 回滚成本高:如果在线DDL操作失败,回滚操作的成本可能很高。
- 可能导致复制延迟:长时间运行的在线DDL操作可能会导致复制延迟。在线DDL操作必须在主服务器上完成后才能在从服务器上运行。此外,在主服务器上并发处理的DML只有在从服务器上的DDL操作完成后才能在从服务器上处理。
有关对大型表运行在线DDL操作的更多信息,请参阅14.13.2节 “在线DDL的性能和并发性”。
14.13.7 在线 DDL 与复制
在线 DDL 操作在主从复制环境中需要特别考虑,以下是相关的详细内容:
复制环境中的基本原理
在主从复制架构里,主服务器上执行的在线 DDL 操作会被复制到从服务器。主服务器上的操作会以二进制日志(binlog)的形式记录下来,从服务器读取这些二进制日志并在自身上重放这些操作,从而保持与主服务器数据的一致性。
不同操作的复制情况
- 原地操作:对于可以原地执行的在线 DDL 操作(如添加虚拟列、修改列的默认值等),通常在主从复制中表现良好。这些操作在主服务器上执行时,对表的修改是直接进行的,不需要复制整个表的数据。从服务器可以快速地重放这些操作,因为它们主要是对元数据的修改,不会产生大量的数据传输和处理开销。
- 重建表操作:当主服务器上执行需要重建表的在线 DDL 操作(如更改列的数据类型、添加或删除索引等)时,会对复制产生较大影响。这类操作需要创建一个新的表结构,并将原表的数据复制到新表中。在主服务器上,这个过程可能会比较耗时,并且会占用大量的系统资源。从服务器需要等待主服务器完成操作并将相关的二进制日志传输过来后,才能开始在自身上重放这些操作。这可能会导致从服务器与主服务器之间出现复制延迟。
复制延迟问题及解决方法
- 问题描述:长时间运行的在线 DDL 操作可能会导致主从复制延迟。这是因为主服务器在执行 DDL 操作时,可能会阻塞后续的 DML 操作,并且 DDL 操作本身也需要一定的时间完成。从服务器需要等待主服务器完成 DDL 操作并将二进制日志传输过来后,才能继续处理后续的 DML 操作,从而导致从服务器的数据与主服务器的数据不一致。
- 解决方法
- 选择合适的时间执行:尽量在业务低谷期执行需要重建表的在线 DDL 操作,以减少对业务的影响。例如,在深夜或周末进行操作,此时系统的负载较低,对用户的影响最小。
- 优化操作:在执行 DDL 操作之前,对表进行优化,如清理无用的数据、重建索引等,以减少操作所需的时间和资源。
- 增加从服务器资源:如果复制延迟问题经常出现,可以考虑增加从服务器的硬件资源,如 CPU、内存和磁盘 I/O 等,以提高从服务器处理 DDL 操作的能力。
在线 DDL 与多源复制
在多源复制环境中,多个主服务器的二进制日志会被复制到同一个从服务器。当多个主服务器同时执行在线 DDL 操作时,可能会出现冲突和复杂的情况。例如,不同主服务器上对同一个表的不同 DDL 操作可能会导致从服务器上的数据不一致。为了避免这种情况,需要仔细规划和协调各个主服务器上的 DDL 操作,确保它们不会相互冲突。
14.13.8 在线 DDL 的监控与调试
为了确保在线 DDL 操作的顺利进行,需要对其进行监控和调试。以下是一些常用的方法和工具:
使用 SHOW PROCESSLIST 命令
SHOW PROCESSLIST
命令可以显示当前 MySQL 服务器上正在执行的所有线程的信息,包括 DDL 操作的线程。通过查看该命令的输出,可以了解 DDL 操作的执行状态,如是否正在等待锁、是否已经完成等。例如:
SHOW FULL PROCESSLIST;
输出结果中会包含每个线程的详细信息,如线程 ID、用户、主机、执行的 SQL 语句、执行时间和状态等。通过检查 State
列,可以了解 DDL 操作的当前状态,如 Waiting for table metadata lock
表示该操作正在等待表的元数据锁。
使用 Performance Schema
Performance Schema 是 MySQL 提供的一个强大的性能监控工具,它可以提供有关在线 DDL 操作的详细信息。通过查询 Performance Schema 中的相关表,可以了解 DDL 操作的执行时间、锁等待时间、I/O 操作等信息。例如:
- 查看阶段事件:可以通过查询
performance_schema.events_stages_current
和performance_schema.events_stages_history
表来查看 DDL 操作的各个阶段的执行情况。
SELECT * FROM performance_schema.events_stages_current WHERE THREAD_ID = (SELECT THREAD_ID FROM performance_schema.threads WHERE PROCESSLIST_ID = <DDL操作的线程ID>);
- 查看锁信息:通过查询
performance_schema.metadata_locks
表,可以了解 DDL 操作所涉及的元数据锁的信息,如锁的类型、持有锁的线程、等待锁的线程等。
SELECT * FROM performance_schema.metadata_locks WHERE OBJECT_NAME = '<表名>';
使用 INFORMATION_SCHEMA
INFORMATION_SCHEMA 是 MySQL 提供的一个系统数据库,它包含了有关数据库、表、列、索引等的元数据信息。通过查询 INFORMATION_SCHEMA 中的相关表,可以了解 DDL 操作对表结构的影响。例如:
SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '<表名>';
该查询可以显示指定表的所有列的信息,包括列名、数据类型、默认值等。通过比较 DDL 操作前后的查询结果,可以了解 DDL 操作对表结构的具体修改。
日志记录
MySQL 的错误日志和二进制日志也可以用于监控和调试在线 DDL 操作。错误日志会记录 DDL 操作过程中出现的错误信息,通过查看错误日志,可以了解操作失败的原因。二进制日志会记录所有的 DDL 操作,通过分析二进制日志,可以了解操作的执行顺序和详细内容。
14.13.9 在线 DDL 的最佳实践
为了确保在线 DDL 操作的顺利进行,并最大限度地减少对业务的影响,以下是一些最佳实践:
提前规划
- 评估操作影响:在执行在线 DDL 操作之前,仔细评估该操作对系统性能、业务可用性和数据一致性的影响。考虑操作所需的时间、资源和可能出现的问题,并制定相应的应对措施。
- 选择合适的时间:尽量在业务低谷期执行需要重建表的在线 DDL 操作,以减少对业务的影响。例如,在深夜或周末进行操作,此时系统的负载较低,对用户的影响最小。
测试环境验证
- 在测试环境中模拟操作:在生产环境中执行在线 DDL 操作之前,先在测试环境中进行模拟操作。测试环境应尽可能与生产环境一致,包括硬件配置、数据库版本、数据量等。通过在测试环境中模拟操作,可以发现潜在的问题,并进行相应的调整和优化。
- 验证操作结果:在测试环境中执行 DDL 操作后,验证操作结果是否符合预期。检查表结构是否正确修改、数据是否完整、业务功能是否正常等。
备份数据
- 执行操作前备份数据:在执行在线 DDL 操作之前,对相关的数据进行备份。备份可以在操作失败时提供恢复数据的手段,确保数据的安全性和完整性。可以使用 MySQL 提供的备份工具,如
mysqldump
或第三方备份工具进行备份。
监控与日志记录
- 实时监控操作过程:在执行在线 DDL 操作时,实时监控操作的执行情况。使用
SHOW PROCESSLIST
、Performance Schema 和 INFORMATION_SCHEMA 等工具,了解操作的执行状态、锁等待时间、I/O 操作等信息。及时发现并处理操作过程中出现的问题。 - 记录操作日志:记录 DDL 操作的详细信息,包括操作的时间、执行的 SQL 语句、操作的结果等。操作日志可以在后续的分析和调试中提供重要的参考信息。
逐步执行操作
- 拆分复杂操作:对于复杂的 DDL 操作,尽量将其拆分为多个简单的操作。例如,将一个包含多个列修改和索引添加的操作拆分为多个单独的操作,逐个执行。这样可以降低操作的风险,并且在出现问题时更容易进行回滚和调试。
- 分阶段执行:如果可能的话,分阶段执行 DDL 操作。例如,先在部分数据上进行操作,验证操作结果后,再在全量数据上执行操作。这样可以减少操作对系统的影响,并且在出现问题时可以及时停止操作。
与业务团队沟通
- 提前通知业务团队:在执行在线 DDL 操作之前,提前通知业务团队操作的时间、内容和可能的影响。让业务团队做好相应的准备,如调整业务流程、安排备用系统等。
- 及时反馈操作结果:在操作完成后,及时向业务团队反馈操作的结果。如果操作过程中出现了问题,及时说明问题的原因和解决情况,确保业务团队对操作的情况有清晰的了解。
通过遵循以上最佳实践,可以有效地提高在线 DDL 操作的成功率,减少对业务的影响,确保数据库系统的稳定运行。