MySQL 8.0 DDL执行时内存暴涨的直接原因不是内存泄漏,而是ALTER TABLE在COPY或INPLACE重建阶段主动申请大量内存用于排序、缓冲和临时索引构建;例如加唯一索引需缓存全部键值,触发COPY会额外占用tmp_table_size空间,INPLACE重建聚簇索引则依赖innodb_sort_buffer_size,默认2MB易撑爆内存。

MySQL 8.0 DDL执行时内存暴涨的直接原因
根本不是“内存泄漏”,而是ALTER TABLE在特定模式下会主动申请大量内存用于排序、缓冲和临时索引构建——尤其是当操作触发COPY或INPLACE中的重建阶段时。
比如对一个2000万行的utf8mb4表加唯一索引,InnoDB必须在校验唯一性过程中缓存所有待插入键值;若sort_buffer_size和innodb_sort_buffer_size设置偏大(如默认1MB或更高),又没做并发控制,多个DDL同时跑就会把可用内存吃光。
- 触发
COPY模式(如修改列类型、变更字符集):全表扫描+逐行写入临时表,innodb_buffer_pool_size之外还要额外占用tmp_table_size和max_heap_table_size空间 - 触发
INPLACE但需重建聚簇索引(如添加主键、修改ROW_FORMAT):内部会启动一个“在线重建”流程,使用innodb_sort_buffer_size做页内排序,该值默认2MB,大表下极易撑爆物理内存 - 隐式转换导致无法复用现有结构:例如
ALTER TABLE t MODIFY c VARCHAR(255) CHARACTER SET utf8mb4,即使原列已是utf8mb4,MySQL 8.0仍可能判定为不兼容而走重建路径
如何判断当前DDL走的是哪种模式
别猜,看INFORMATION_SCHEMA.INNODB_METRICS或执行SHOW ENGINE INNODB STATUS\G里TRANSACTIONS部分的lock_wait和undo log增长趋势;更直接的是查performance_schema.events_statements_history_long,过滤SQL_TEXT含ALTER TABLE的记录,看WORK_COMPLETED和WORK_ESTIMATED字段——如果后者远大于前者且持续上涨,基本就是COPY或重建类操作。
注意:ALGORITHM=INSTANT只适用于极少数操作(如加列、删列、重命名列),且要求表无全文索引、无虚拟列、行格式为DYNAMIC。一旦不满足,MySQL 8.0会自动降级到INPLACE甚至COPY,但不会报错,只在Warning里提示:
Warning 1846 ALGORITHM=INSTANT is not supported for this operation. Reverting to ALGORITHM=INPLACE.
内存暴涨时的应急与预防措施
线上DDL中途发现内存飙升,不要kill -9 mysqld进程——这大概率导致表空间损坏。正确做法是:KILL QUERY对应连接ID(从SHOW PROCESSLIST找状态为altering table的线程),让MySQL自行回滚(耗时可能很长,但安全)。
- 执行前用
EXPLAIN FORMAT=TREE预判:虽然不支持ALTER,但可先对等效SELECT语句分析,确认是否涉及全表扫描或大排序 - 调低临时参数:
SET SESSION innodb_sort_buffer_size = 262144(256KB)、SET SESSION sort_buffer_size = 131072(128KB),再执行DDL - 避开高峰时段,并监控
Threads_running和Innodb_buffer_pool_pages_free,低于总页数5%时强制暂停 - 对超大表,优先考虑
pt-online-schema-change或gh-ost——它们把内存压力分散到应用层,不依赖MySQL内部缓冲区
容易被忽略的字符集陷阱
utf8mb4列建索引时,MySQL按字节数算长度。一个VARCHAR(191)字段在utf8mb4下最大占764字节(191×4),刚好卡在旧索引限制边缘;但MySQL 8.0默认启用innodb_large_prefix,理论上支持3072字节索引前缀——前提是ROW_FORMAT=DYNAMIC且innodb_file_format=Barracuda。如果表是升级遗留下来的COMPACT格式,加索引仍会触发重建,内存开销翻倍。
验证方式:SELECT ROW_FORMAT FROM INFORMATION_SCHEMA.INNODB_TABLES WHERE NAME='your_db/your_table';。不是DYNAMIC?先ALTER TABLE ... ROW_FORMAT=DYNAMIC,再做其他DDL——否则每次都在重复踩坑。


















