本质是MDL锁或IO瓶颈卡在元数据层或IO层;State为“Waiting for table metadata lock”即MDL阻塞,有长事务挡路;为“copy to tmp table”或“Sorting”则属IO/内存瓶颈。

大表执行 ALTER TABLE 时写阻塞,本质不是“改结构慢”,而是锁等待或排序瓶颈卡在元数据层或IO层;直接调参数或换语法不一定能解,得先判断卡在哪一环。
怎么快速定位是MDL锁还是IO瓶颈?
执行 SHOW PROCESSLIST 后,重点看两列:
- State 为
Waiting for table metadata lock→ 元数据锁(MDL)阻塞,源头是未提交事务(哪怕只是SELECT) - State 为
copy to tmp table或长时间停在Sorting for table→ 排序阶段卡住,sort_buffer_size不够或tmpdir磁盘满
再补查一句:SELECT * FROM information_schema.INNODB_TRX WHERE TIME_TO_SEC(NOW()) - TIME_TO_SEC(TRX_STARTED) > 60;,有结果就说明长事务正在挡路。
MySQL 5.7+ 能用 ALGORITHM=INPLACE, LOCK=NONE 吗?
能用 ≠ 默认启用,必须显式指定,且仅对部分操作生效:
- 加
NULL字段、改默认值、加普通索引 → 支持LOCK=NONE - 加
NOT NULL字段(无默认值)、改列类型(如VARCHAR(255)→VARCHAR(50))、删列 → 必须LOCK=SHARED或更严,写仍会被阻塞 - MySQL 5.6 不支持
INPLACE,强制走COPY,全表锁死
验证是否真生效:执行后立刻查 SHOW ENGINE INNODB STATUS\G,搜索 DDL log,有输出说明走的是在线路径;若没日志且 Processlist 显示锁表,说明被降级了。
为什么 pt-online-schema-change 有时反而更慢?
它不锁表,但引入新瓶颈:
- 触发器开销:每条
INSERT/UPDATE/DELETE都要额外触发一次写影子表,QPS 高时 CPU 和延迟明显上涨 - 主从延迟放大:原表变更通过 binlog 同步到从库,而工具自己又在从库重放影子表变更,延迟可能翻倍
- 不能中途停:一旦启动,
DROP TRIGGER会中断同步,导致数据不一致;必须等--execute完成才能停 - 不兼容全文索引、生成列、外键约束多的表,报错退出
建议只在真正无法接受秒级写阻塞、且表结构简单(无触发器、无复杂约束)时启用;执行前务必加 --dry-run 和 --print 看它实际建什么语句。
真正安全又可控的折中方案是什么?
对千万级以上大表,别赌 INPLACE 是否生效,也别盲目上 pt-osc:
- 先用
SELECT COUNT(*)估算行数,再按 SSD 环境每百万行 30–90 秒预估耗时 - 执行前
SET SESSION sort_buffer_size = 8388608; SET SESSION read_rnd_buffer_size = 8388608;,并确认tmpdir在 SSD 且剩余空间 > 表大小 × 1.5 - 加字段优先用
ADD COLUMN col_name TYPE NULL DEFAULT ...,避免NOT NULL强制全表扫描赋值 - 如果业务能容忍几秒写失败,就用原生
ALTER TABLE ... ALGORITHM=COPY, LOCK=NONE(MySQL 5.6+),它不依赖排序,内存压力小,失败也容易回退
最易被忽略的一点:ALGORITHM=COPY, LOCK=NONE 要求 innodb_online_alter_log_max_size 足够大(默认 128MB),否则 DDL 中途因日志溢出静默失败,表可能残留临时文件,需手动清理 #sql-* 文件并重启 MySQL。











