ddl卡住时所有连接均显示“waiting for table metadata lock”,是因为ddl需x mdl锁而被持有s mdl或shared_read_only锁的会话阻塞,导致后续所有对该表的读写及ddl操作均排队等待。

DDL 卡住时为什么所有连接都卡在 “Waiting for table metadata lock”
不是 DDL 自己卡住了,而是它在等一个排他元数据锁(X MDL),而这个锁被另一个会话长期持有着——只要那个会话没释放 S MDL 或 SHARED_READ_ONLY 锁,所有新来的操作(包括 SELECT、INSERT、UPDATE、其他 ALTER)都会排队等待,状态统一显示为 Waiting for table metadata lock。
常见持有者包括:
- 未提交的事务里执行过
SELECT FOR UPDATE或普通SELECT(哪怕只查一行,只要事务开着,S MDL 就不释放) - 显式执行了
LOCK TABLES t READ但忘了UNLOCK TABLES - 从库 SQL 线程卡在回放某个 DDL,导致该表的 MDL 一直被占着,主库连过来的新查询也全被拦住
- 存储过程中调用了
SELECT后没 COMMIT,或者异常退出没 rollback
为什么一个表的 DDL 会影响整个实例的读写
MySQL 的 MDL 是全局的、跨库的。一旦某张表(比如 orders)被一个长事务或阻塞会话锁住,所有后续对该表的访问——不管来自哪个数据库、哪个用户、什么命令类型——都会被挡在 Opening tables 阶段。更麻烦的是:如果这张表被高频访问(如订单中心的主表),大量应用连接会堆积在等待队列里,连接数快速涨到 max_connections 上限,新连接直接被拒绝,看起来就像“整个实例不可用”。
这不是 IO 或 CPU 扛不住,是锁调度器在死等。你看到 SHOW PROCESSLIST 里一堆 Sleep 或 Waiting 状态,但没一条在真正干活。
如何快速定位谁在 hold 锁而不是杀错人
别一上来就 KILL 那个写着 altering table 的线程——它大概率是受害者。真正要找的是那个“拿了锁不放”的源头:
- 查阻塞关系:
SELECT * FROM sys.schema_table_lock_waits WHERE waiting_query LIKE 'alter%';,结果里的sql_kill_blocking_connection字段直接给出该 kill 的 ID - 手动关联(无 sys 库时):
SELECT blocking_trx_id, blocked_trx_id FROM performance_schema.data_lock_waits;,再 JOINperformance_schema.data_locks找出OWNER_THREAD_ID - 确认是否真有悬停事务:
SELECT trx_id, trx_state, trx_started, trx_mysql_thread_id FROM information_schema.INNODB_TRX ORDER BY trx_started LIMIT 5;,看有没有running状态但Time超过几分钟的
DDL 本身要不要锁表,取决于你用什么方式执行
不是所有 DDL 都必然锁表,但能否避开锁,完全取决于操作类型 + 存储引擎 + 参数组合。InnoDB 支持 ALGORITHM=INPLACE,但只有少数操作能真正实现 LOCK=NONE:
- 安全项:
ADD INDEX、DROP INDEX、ADD COLUMN(加在末尾)、ALTER COLUMN SET DEFAULT - 危险项:
MODIFY COLUMN、CHANGE COLUMN、ADD COLUMN插入非末尾位置、ENGINE=InnoDB(即使原就是 InnoDB)——这些会降级为COPY模式,全程锁表 - 永远别信“加个字段很快”,千万级表上
ADD COLUMN不加ALGORITHM=INPLACE, LOCK=NONE就等于主动停服
最隐蔽的坑是:你写了 ALGORITHM=INPLACE,但字段类型变更触发了隐式降级,MySQL 不报错也不警告,只是默默切到 COPY 模式——得靠 SHOW ENGINE INNODB STATUS 末尾的 DDL 日志确认实际执行路径。











