Waiting for table metadata lock 表示ALTER TABLE因无法获取MDL_EXCLUSIVE锁而阻塞,真正持锁者是Command='Sleep'、Time>300、Info=NULL的悬挂事务连接;须通过performance_schema.metadata_locks查LOCK_STATUS='GRANTED'定位PROCESSLIST_ID,并结合INNODB_TRX验证trx_state='RUNNING'后优先KILL QUERY。

它不是“慢”,是被别的连接死死卡住了——Waiting for table metadata lock 这个状态本身不表示你在执行的 ALTER TABLE 有多耗时,而是说它根本拿不到元数据锁(MDL),连开始都做不到。
谁在持锁?别看 Waiting 那行
显示 Waiting for table metadata lock 的线程只是受害者。真正持锁的是那些安静挂着的 Sleep 连接,尤其是:
• Command = 'Sleep'
• Time > 300(5 分钟以上)
• Info = NULL
这种连接大概率开了事务但没提交,从 BEGIN 起就一直拿着 MDL_SHARED_READ 锁不放,而 ALTER TABLE 需要 MDL_EXCLUSIVE,二者互斥。
查 INNODB_TRX 不够准,因为自动提交语句、ORM 隐式事务、甚至未关闭游标的查询都可能留下 MDL S 锁但不显现在事务表里。必须用 performance_schema.metadata_locks:
- 先确认开关已开:
SELECT * FROM performance_schema.setup_instruments WHERE NAME = 'wait/lock/metadata/sql/mdl';,确保ENABLED和TIMED都是YES - 再查真正在 hold 锁的:
SELECT OBJECT_SCHEMA, OBJECT_NAME, LOCK_TYPE, LOCK_STATUS, PROCESSLIST_ID FROM performance_schema.metadata_locks m JOIN performance_schema.threads t ON m.OWNER_THREAD_ID = t.THREAD_ID WHERE OBJECT_SCHEMA = 'your_db' AND OBJECT_NAME = 'your_table' AND LOCK_STATUS = 'GRANTED'; - 只关注
LOCK_STATUS = 'GRANTED'的结果,对应PROCESSLIST_ID就是持锁线程 ID
KILL 前先试 KILL QUERY,别直接干掉连接
拿到持锁线程 ID 后,别一上来就 KILL [id]。先执行 KILL QUERY [id],只终止当前语句,保留连接和事务上下文。如果它卡在空事务里(TRX_QUERY 为空),KILL QUERY 通常无效,再考虑 KILL [id]。
验证是否真在持锁:
• 查 information_schema.INNODB_TRX:SELECT * FROM information_schema.INNODB_TRX WHERE trx_mysql_thread_id = [id] AND trx_state = 'RUNNING';
• 如果返回结果且 trx_started 时间远超业务预期(比如 >60 秒),基本可确认是悬挂事务
• 注意:SHOW OPEN TABLES WHERE In_use > 0 对 MDL 锁完全无效,别浪费时间
ALGORITHM=INPLACE 不等于不锁,得看操作类型
ALGORITHM=INPLACE 和 LOCK=NONE 不是万能解药,MySQL 会严格校验是否支持,不满足就报错,不会静默降级:
-
ADD COLUMN在末尾添加、无NOT NULL DEFAULT:MySQL 5.6+ 支持 inplace,但仍有短暂排他 MDL 期 -
MODIFY COLUMN改长度或字符集(如VARCHAR(255) → VARCHAR(1024)):MySQL 5.7 必重建;8.0.12+ 仅当长度头不变、字符集不变、无存储格式升级时才可能 inplace -
ADD INDEX:一般支持LOCK=NONE,但如果字段没走索引扫描、含BLOB或建唯一索引时可能降级 - 执行前务必用
EXPLAIN FORMAT=JSON确认"alter_algorithm": "inplace"和"supports_inplace": true
最麻烦的点往往不在 ALTER 本身,而在那个没人记得关的连接、那个 ORM 自动开启却忘了 commit 的事务、或者那个连 SELECT 都没写完就挂掉的客户端。定位时别只盯着 SHOW PROCESSLIST 里最显眼的 Waiting 行,得顺着 metadata_locks 找到真正安静持锁的线程 ID,再结合 INNODB_TRX 确认它是不是真的在跑一个“空跑”事务——这才是多数线上卡死的真实起点。


















