alter table卡在“waiting for table metadata lock”是因未提交事务持有mdl读锁,阻塞ddl所需排他锁;需查innodb_trx和processlist定位超时事务,kill对应线程id而非等待线程。

表结构变更不会“失败”,但会被未提交事务阻塞——你看到的卡住、超时、500,本质是MDL锁等待,不是ALTER执行出错。
为什么ALTER TABLE会卡在“Waiting for table metadata lock”
MySQL 执行 ALTER TABLE 时必须获取元数据锁(MDL),而任何未提交的事务(哪怕只是个没结束的 SELECT FOR UPDATE 或慢查询)都会持续持有该表的 MDL 读锁,导致 DDL 被挂起。这不是语法错误,也不是权限问题,是并发控制机制的正常表现。
常见诱因包括:
- 应用层开启事务后忘记
COMMIT或ROLLBACK,且连接被连接池复用 - 监控脚本或 DBA 手动执行了长
SELECT并未关闭会话 - 应用异常中断(如 JVM crash、网络断连),导致事务残留但连接未释放
- MySQL 8.0+ 中
ALGORITHM=INSTANT操作虽快,仍需先抢到 MDL,一样会被堵
如何快速定位并清理阻塞源
别猜,直接查活跃事务和锁状态:
- 运行
SELECT * FROM information_schema.INNODB_TRX WHERE TIME_TO_SEC(NOW()) - TIME_TO_SEC(TRX_STARTED) > 60;找出运行超 1 分钟的事务 - 配合
SELECT * FROM information_schema.PROCESSLIST WHERE COMMAND != 'Sleep' AND TIME > 60;看对应线程的 SQL 和状态 - 重点检查
STATE列为Locked或Waiting for table metadata lock的行 - 确认无业务影响后,用
KILL <code>trx_mysql_thread_id; 终止源头线程(注意:不是 KILL CONNECTION,是 KILL 线程 ID)
如果不敢 kill,可临时降低 wait_timeout(仅对新连接生效),或让应用侧主动回收空闲连接。
ALTER 执行后 INSERT 却“没保存”,其实是事务状态混乱
很多人以为“结构改了,数据却写不进去”,真实原因是:ALTER TABLE 自身会隐式提交当前事务,后续 INSERT 若复用同一连接且未重置状态,可能落在一个已关闭或未开启的事务上下文中,导致静默丢弃。
- 执行前先查
SELECT @@autocommit, @@in_transaction;—— 若autocommit=0,DDL 后务必手动START TRANSACTION再写入 - JDBC 场景下,预编译语句(
PreparedStatement)可能缓存旧表结构,需调用connection.clearCache()或重建 statement - 新增了
NOT NULL字段但没设DEFAULT?所有 INSERT 必须显式提供值,否则报Column 'xxx' cannot be null,不是锁问题,是约束校验
真正安全的线上变更,绕开 MDL 是唯一解
原生 ALTER TABLE 在大表或高并发下风险不可控。不要寄希望于“挑个低峰期试试”,要默认按“不可中断”设计:
- 优先用
pt-online-schema-change:它建影子表 + 触发器同步增量,全程不锁原表;但注意触发器有性能开销,且主从延迟会被放大 - 或用
gh-ost:基于 binlog 解析,无触发器,更轻量;但要求binlog_format=ROW,且不支持外键级联操作 - 自己手写影子表方案时,务必验证
RENAME TABLE原子性,并预留回滚路径(如提前CREATE TABLE t_bak LIKE t)
DDL 不参与事务,没有 ROLLBACK 这回事。所谓“回滚结构变更”,本质是人工执行反向 DDL —— 而一旦字段被删、类型被缩容、索引被去重,原始数据大概率已不可逆丢失。











