MySQL DDL卡住表主因是默认COPY算法锁表,虽5.6+支持ALGORITHM=INPLACE,但字段类型变更、加唯一索引等会降级;需显式指定ALGORITHM=INPLACE, LOCK=NONE(仅部分操作支持),并检查引擎、长事务及磁盘空间。

DDL操作卡住整个表,是因为默认用了COPY算法
MySQL 5.6+ 支持 ALGORITHM=INPLACE,但不是所有 DDL 都自动走它。比如 ALTER TABLE t ADD COLUMN c INT 在多数情况下能原地执行,但一旦涉及字段类型变更(如 VARCHAR(255) → VARCHAR(500))、加索引(尤其唯一索引)、或修改主键,MySQL 可能悄悄回退到 COPY 模式——这时会锁表、复制全量数据,阻塞所有读写。
关键判断依据是执行 SHOW PROCESSLIST 时看到状态为 copy to tmp table,或慢查询日志里出现 alter table ... copy。
- 用
ALGORITHM=INPLACE并不保证成功,MySQL 会校验是否支持;不支持时直接报错ALGORITHM=INPLACE is not supported for this operation,不会静默降级 - 必须搭配
LOCK=NONE才真正避免锁表(仅限支持的场景),否则即使INPLACE也可能加SHARED或EXCLUSIVE锁 -
INPLACE不等于“零影响”:仍需元数据锁(MDL),如果此时有长事务正在查这张表,ALTER会被堵在等待 MDL 阶段,表现为“卡住不动”
哪些DDL操作真正支持 ALGORITHM=INPLACE + LOCK=NONE
不是所有“看起来轻量”的操作都支持无锁。以 InnoDB 表为例,以下操作在 MySQL 8.0 中确认可同时指定 ALGORITHM=INPLACE, LOCK=NONE:
- 添加普通二级索引:
ALTER TABLE t ADD INDEX idx_name (col) - 删除索引:
ALTER TABLE t DROP INDEX idx_name - 添加虚拟列(
GENERATED ALWAYS AS)并建索引 - 扩大
VARCHAR长度(仅限 utf8mb4 字符集且未超行限制) - 修改列的
DEFAULT值(不改类型、不改是否允许 NULL)
以下操作明确不支持 LOCK=NONE,哪怕加了也会报错:ADD COLUMN 非末尾位置、MODIFY COLUMN 类型变更、CHANGE COLUMN、DROP COLUMN、任何主键变更。此时只能接受 LOCK=SHARED(允许读,阻塞写)或放弃无锁目标。
执行前必须检查的三件事
光写对语法远远不够,生产环境出问题往往卡在这几步:
- 确认当前表引擎是
InnoDB(MyISAM完全不支持INPLACE):SHOW CREATE TABLE t查ENGINE=InnoDB - 检查是否有活跃长事务:
SELECT * FROM information_schema.INNODB_TRX WHERE TIME_TO_SEC(NOW() - trx_started) > 60;若有,ALTER会等它结束 - 验证磁盘空间是否足够:即使
INPLACE,加索引仍需临时排序空间,tmpdir或innodb_tmpdir不足会导致失败,错误提示常为Operating system error number 28(no space left)
线上执行时的最小风险姿势
别信“测试库跑过就没事”。真实负载下,MDL 等待、I/O 压力、从库延迟都会放大风险:
- 始终显式写全:
ALTER TABLE t ALGORITHM=INPLACE, LOCK=NONE, ADD INDEX idx_x (x);不省略LOCK=NONE,因为默认值是DEFAULT(由 MySQL 自行决策) - 加索引优先用
ALGORITHM=INPLACE,但删索引更稳妥——它几乎总是INPLACE且极快,不用额外验证 - 对不确定的操作,先在从库上试:
STOP SLAVE; ALTER ...; START SLAVE;,观察同步延迟和错误日志 - 监控
performance_schema.table_lock_waits_summary_by_table,看执行期间是否有其他线程被该表锁阻塞
最常被忽略的是:ALGORITHM=INPLACE 只解决“数据拷贝层”的锁,但 DDL 本身需要持有 MDL 写锁,只要有一个慢查询没结束,整个 ALTER 就挂在那里不动——你看到的“没反应”,很可能不是 MySQL 在干活,而是在干等。











