DDL是MySQL中最常见的变更动作,MySQL发展至今,DDL能力也在不断增强。本文主要介绍MySQL DDL能力的发展过程,常用的DDL操作说明,并给出AliSQL中DDL操作的优化和最佳实践。
DDL能力发展
-
MySQL 5.5及之前:仅支持COPY算法。
-
执行过程:先创建临时表,然后按行拷贝数据,最后表交换。
-
限制:DDL执行期间表只读,无法写入数据。
-
-
MySQL 5.6 & 5.7:支持INPLACE算法。
-
执行过程:引擎层处理DDL,无需临时表。
-
优势:DDL执行期间支持读写数据表。
-
限制:在DDL开始和结束时,可能会短暂锁表(不可读写),且会消耗临时空间。
-
-
MySQL 8.0、8.4:支持INSTANT算法。
-
执行过程:仅修改元数据,无需数据拷贝。
-
优势:秒级完成,显著缓解DDL对业务的影响。
-
限制:支持的DDL类型有限(加列、删列),在DDL开始和结束时,仍需要短暂持有表上的MDL X锁(不可读写)。
-

所有算法均需在DDL的开始和结束阶段(上图阶段1和阶段3)获取MDL X锁,导致短暂锁表(不可读写),AliSQL对MDL锁等待进行了优化,详见AliSQL DDL相关优化功能。
MySQL常用DDL操作说明
MySQL支持在执行DDL操作时指定算法,若不指定,MySQL默认根据DDL操作类型选择最佳算法。以下为常见操作的执行逻辑及影响(默认InnoDB引擎)。
表操作
|
DDL操作 |
DDL命令 |
默认算法及是否支持修改 |
DDL执行期间读写属性 |
可能影响说明 |
|
重命名表 |
RENAME |
支持修改算法,默认值:
|
读写 |
仅修改元数据,无影响。 |
|
重建表 |
OPTIMIZE / ALTER ENGINE |
不支持修改算法,5.6、5.7、8.0和8.4默认值:INPLACE。 |
读写 说明
AliSQL 8.0、8.4数据归档期间表只读不可写。 |
|
|
更新统计信息 |
ANALYZE |
不支持修改算法。 |
读写 |
仅更新统计信息,但某些场景可能导致业务阻塞,详见特殊情况说明页签里的ANALYZE TABLE的阻塞场景。 |
|
修改字符集 |
CONVERT CHARSET |
|
只读,无法进行写操作。 |
|
|
修改表备注 |
ALTER COMMENT |
不支持修改算法,5.6、5.7、8.0和8.4默认值:INPLACE。 |
读写 |
仅修改元数据,无影响。 |
列操作
|
DDL操作 |
DDL命令 |
默认算法及是否支持修改 |
DDL执行期间读写属性 |
可能影响说明 |
|
重命名列 |
RENAME |
支持修改算法,默认值:
|
读写 |
仅修改元数据,无影响。 |
|
增加列 |
ADD |
支持修改算法,默认值:
|
读写 |
|
|
删除列 |
DROP |
支持修改算法,默认值:
|
读写 |
|
|
修改列类型 |
CHANGE / MODIFY |
支持修改算法,5.6、5.7、8.0和8.4默认值:COPY,VARCHAR特殊场景支持INPLACE,详见修改VARCHAR类型字段长度时使用INPLACE算法。 |
只读,无法进行写操作。 |
|
|
修改列字符集 |
ALTER CHARSET |
|
只读,无法进行写操作。 |
|
|
修改列备注 |
ALTER COMMENT |
支持修改算法,默认值:
|
读写 |
仅修改元数据,无影响。 |
索引操作
|
DDL操作 |
DDL命令 |
默认算法及是否支持修改 |
DDL执行期间读写属性 |
可能影响说明 |
|
重命名索引 |
RENAME |
支持修改算法,默认值:
|
读写 |
仅修改元数据,无影响。 |
|
创建索引 |
ADD / CREATE |
支持修改算法,5.6、5.7、8.0和8.4默认值:INPLACE。 |
读写 |
|
|
删除索引 |
DROP |
支持修改算法,5.6、5.7、8.0和8.4默认值:INPLACE。 |
读写 |
仅修改元数据,无影响。 |
特殊情况说明
修改VARCHAR类型字段长度时使用INPLACE算法
VARCHAR类型字段长度存在一个临界值,当您需要使用INPLACE算法修改VARCHAR类型字段长度时,必须保证修改前的长度和修改后的长度,均小于等于该临界值,或均大于该临界值。如需跨临界值变更,则需要使用COPY算法。不同字符集对应的临界值(字符数)不同:
|
字符集 |
临界值(字符数) |
长度变更区间 |
可选算法 |
|
utf8 / utf8mb3 |
85 |
修改前后长度均在 |
INPLACE、COPY |
|
修改前后长度均在 |
|||
|
跨临界值变更 |
COPY |
||
|
utf8mb4 |
63 |
修改前后长度均在 |
INPLACE、COPY |
|
修改前后长度均在 |
|||
|
跨临界值变更 |
COPY |
||
|
latin1 |
255 |
修改前后长度均在 |
INPLACE、COPY |
|
修改前后长度均在 |
|||
|
跨临界值变更 |
COPY |
修改字符集(CONVERT CHARSET)时使用INPLACE算法
字符集修改操作会对表中所有带字符集信息的列进行修改,MySQL当前包含字符集信息的列有CHAR、VARCHAR、TEXT、ENUM和SET。当您需要使用INPLACE算法修改字符集时,需要满足以下条件:
-
MySQL版本为8.0及以上。
-
如果表中存在CHAR或VARCHAR类型字段时,该列不能为索引(或索引的一部分),且列长度需要小于等于两个临界值(修改前后字符集临界值)中的最小值,或大于两个临界值中的最大值。
以utf8 / utf8mb3字符集变更为utf8mb4字符集为例:
-
utf8 / utf8mb3字符集临界值为85。 -
utf8mb4字符集临界值为63。
当表内含有CHAR或VARCHAR类型字段且不为索引列时,对应字段长度需要在(0,63]或(85,65535]区间内才能使用INPLACE算法修改字符集。
ANALYZE TABLE的阻塞场景
-
问题:若表存在未结束的慢SQL,ANALYZE后所有表访问需等待慢SQL结束才能刷新表缓存。
-
解决方案:MySQL 8.0、8.4已修复此问题。
DDL执行时间评估
方式一:基于Performance Schema(PFS)评估
从MySQL 5.7版本开始在 PFS加入了DDL相关Stage统计信息,可以通过processlist和P_S库下的events_stages_current表查看DDL进度,并且评估剩余时间。
-
适用场景:DDL执行中需实时监控进度。
-
查询语句:
SELECT * FROM information_schema.processlist WHERE ID = <替换为实际ID>; SELECT THREAD_ID, EVENT_ID, EVENT_NAME, WORK_COMPLETED, WORK_ESTIMATED,(WORK_COMPLETED/WORK_ESTIMATED)*100 AS PROGRESS FROM performance_schema.events_stages_current WHERE THREAD_ID = <替换为实际ID>;说明执行
SHOW Processlist命令根据执行的SQL获取ID。 -
示例:本地Sysbench 10 GB的表,查询结果
TIME=116,PROGRESS=83.9961%,如下所示:mysql> SELECT * FROM information_schema.processlist WHERE ID = 18; +----+------+-----------+----------+---------+------+----------------+------------------------+ | ID | USER | HOST | DB | COMMAND | TIME | STATE | INFO | +----+------+-----------+----------+---------+------+----------------+------------------------+ | 18 | root | localhost | ddl_test | Query | 116 | altering table | optimize table sbtest1 | +----+------+-----------+----------+---------+------+----------------+------------------------+ 1 row in set (0.00 sec) mysql> SELECT THREAD_ID,EVENT_ID, EVENT_NAME, WORK_COMPLETED, WORK_ESTIMATED,(WORK_COMPLETED/WORK_ESTIMATED)*100 as PROGRESS FROM performance_schema.events_stages_current WHERE THREAD_ID = 32; +-----------+----------+----------------------------------------+----------------+----------------+----------+ | THREAD_ID | EVENT_ID | EVENT_NAME | WORK_COMPLETED | WORK_ESTIMATED | PROGRESS | +-----------+----------+----------------------------------------+----------------+----------------+----------+ | 32 | 127 | stage/innodb/alter table (insert) | 2273884 | 2707129 | 83.9961 | +-----------+----------+----------------------------------------+----------------+----------------+----------+ 1 row in set (0.00 sec)-
计算时间 = TIME / PROGRESS= 116 / (83.9961%) = 138.1 s -
实际执行时间:2 min 3.14 s = 123.14 s
执行
optimize table sbtest1;命令,实际耗时 2 分 3.14 秒(约 123 秒),与 PFS 评估的 138.1 秒基本吻合。mysql> mysql> optimize table sbtest1; +------------------+----------+----------+-------------------------------------------------------------------+ | Table | Op | Msg_type | Msg_text | +------------------+----------+----------+-------------------------------------------------------------------+ | ddl_test.sbtest1 | optimize | note | Table does not support optimize, doing recreate + analyze instead | | ddl_test.sbtest1 | optimize | status | OK | +------------------+----------+----------+-------------------------------------------------------------------+ 2 rows in set (2 min 3.14 sec)
-
方式二:基于表空间大小评估
-
适用场景:无需开启PFS(消耗内存且会有性能影响,RDS MySQL默认关闭)的估算。
-
步骤:
-
查询表大小:
SELECT * FROM information_schema.tables WHERE TABLE_SCHEMA = <替换为实际的数据库名> AND TABLE_NAME = <替换为实际的表名>; -
根据查询到的DATA_LENGTH大小,直接估算DDL的时间,以OPTIMIZE TABLE操作为例:
-
云盘实例(高性能云盘和ESSD云盘):按照30~60 MB/s的速度进行估算。
-
高性能本地盘实例:按照50~100 MB/s的速度进行估算。
说明30~60 MB/s和50~100 MB/s的速度是根据实例的IO能力估算的值,实际可能会有偏差。
-
-
(可选)索引影响:如果表存在很多二级索引,还需考虑二级索引排序和构建的时间,按主表速度的1/3估算索引构建时间。
-
-
示例:本地Sysbench 10 GB的表,查询结果如下:
mysql> SELECT * from information_schema.tables WHERE TABLE_SCHEMA = 'ddl_test' AND TABLE_NAME = 'sbtest1'\G *************************** 1. row *************************** TABLE_CATALOG: def TABLE_SCHEMA: ddl_test TABLE_NAME: sbtest1 TABLE_TYPE: BASE TABLE ENGINE: InnoDB VERSION: 10 ROW_FORMAT: Dynamic TABLE_ROWS: 39452112 AVG_ROW_LENGTH: 260 DATA_LENGTH: 10270785536 MAX_DATA_LENGTH: 0 INDEX_LENGTH: 665829376 DATA_FREE: 3145728 AUTO_INCREMENT: 40000001 CREATE_TIME: 2025-05-09 15:58:33 UPDATE_TIME: NULL CHECK_TIME: NULL TABLE_COLLATION: utf8_general_ci CHECKSUM: NULL CREATE_OPTIONS: TABLE_COMMENT: 1 row in set (0.00 sec)-
计算时间 = DATA_LENGTH / 1024 / 1024 / 100 + INDEX_LENGTH / 1024 / 1024 / 100 * 3= 10270785536 / 1024 / 1024 / 100 + 665829376 / 1024 / 1024 / 100 * 3 = 117 s -
实际执行时间:2 min 3.14 s = 123.14 s
-
AliSQL DDL最佳实践
基本原则
-
DDL是变更操作,尽量避开业务高峰。
-
支持INSTANT算法的DDL,或其他仅修改元数据的DDL,可以放心执行(为避免操作开始和结束阶段无法获取MDL X锁,仍需重点关注慢查询和大事务)。
-
只支持COPY算法的DDL,执行过程中表只读,无法进行写操作,需要谨慎执行。
-
其他支持INPLACE算法的DDL,执行过程中表可以读写,但是需要综合评估执行时间、空间占用,以及可能导致的复制延迟问题。
AliSQL DDL相关优化功能
-
-
功能:异步清理大文件,避免文件系统抖动。
-
参数:
innodb_data_file_purge=ON(默认开启)。
-
-
-
功能:减少DDL的执行耗时,降低DDL操作带来的性能影响。
-
参数:
loose_innodb_rds_faster_ddl=ON(需手动开启)。
-
-
-
功能:元数据变更,无需重建表。
-
参数:
loose_innodb_instant_ddl_enabled=ON(MySQL 8.0版本默认开启,5.7及以下版本需手动开启)。
-
-
其他优化
-
AHI清理优化:优化删除表和索引过程中AHI清理导致的性能问题。参数:innodb_rds_drop_ahi_ahead,默认为OFF。
-
唯一索引冲突优化:优化DDL期间唯一索引冲突导致DDL失败的问题。
-
MDL锁等待逻辑优化:将MDL X锁的阻塞式等待优化为非阻塞式等待,解决了DDL长期拿不到MDL锁导致业务面积性阻塞的问题。
-
ANALYZE TABLE优化:解决了MySQL 5.7 版本ANALYZE TABLE可能导致业务阻塞的问题。
-
Parallel DDL:修复DDL性能回退问题。
-
相关文档
-
升级至AliSQL最新版本以获取全部优化功能