根本原因是MDL锁机制等待已持有MDL_SHARED_READ锁却未释放的会话,常见阻塞源包括未提交的SELECT、空闲Sleep连接及ORM隐式事务未COMMIT;加字段前须查长事务、MDL阻塞链和可疑空闲连接,并显式使用ALGORITHM=INPLACE、LOCK=NONE及DEFAULT NULL;lock_wait_timeout建议设为30秒以快速暴露问题。

为什么ALTER TABLE会卡住Waiting for table metadata lock
根本原因不是“MySQL太慢”,而是MDL锁机制在等一个已经持有了MDL_SHARED_READ锁却迟迟不释放的会话。常见阻塞源包括:未提交的SELECT(尤其带SLEEP()或大结果集)、应用端连接没关闭(Command=Sleep但Time很大)、ORM隐式开启事务后忘记COMMIT。一旦它存在,后续所有ALTER TABLE、CREATE INDEX甚至普通SELECT都会排队等待——这不是DDL本身慢,是别人占着锁不放。
加字段前必须做的三件事
别急着敲ALTER TABLE,先确认环境是否可控:
- 查当前是否有长事务:
SELECT * FROM information_schema.INNODB_TRX WHERE TIME_TO_SEC(TIMEDIFF(NOW(), trx_started)) > 60; - 查MDL阻塞链:
SELECT * FROM performance_schema.metadata_locks ml JOIN performance_schema.threads t ON ml.OWNER_THREAD_ID = t.THREAD_ID WHERE ml.LOCK_STATUS = 'PENDING' AND ml.OBJECT_SCHEMA = 'your_db'; - 查可疑空闲连接:
SHOW PROCESSLIST;中Command为Sleep且Time超过300秒的,大概率是应用没关连接
MySQL 5.6/5.7下安全加字段的实操参数组合
即使表有2000万行,只要满足“新增普通字段+不改列顺序+无默认值或显式指定DEFAULT NULL”,就能走INPLACE算法。关键是要显式声明,否则MySQL可能退化为COPY:
- 强制走Online DDL:
ALGORITHM=INPLACE - 尽量不锁表:
LOCK=NONE(注意:若字段不允许为NULL且无默认值,LOCK=NONE会失败,此时改用LOCK=SHARED) - 加上
DEFAULT NULL显式声明,默认值能避免全表更新触发拷贝
完整示例:ALTER TABLE orders ADD COLUMN remark TEXT DEFAULT NULL, ALGORITHM=INPLACE, LOCK=NONE;
lock_wait_timeout设多少才真正有用
设成31536000(一年)等于没设——它只控制“新申请锁的线程”最多等多久,不杀已存在的阻塞源。线上建议直接设为30:
- 临时生效(当前会话):
SET lock_wait_timeout = 30; - 全局生效(新连接):
SET GLOBAL lock_wait_timeout = 30; - 永久生效:在
my.cnf的[mysqld]段加lock_wait_timeout = 30,重启
这样,当DDL因长事务卡住时,30秒后直接报错ERROR 1205 (HY000): Lock wait timeout exceeded,而不是让后续所有查询跟着挂起。但注意:这不能解决阻塞根源,只是让问题暴露得更快。
真正难的是定位那个不肯释放MDL的会话——它可能藏在应用层、ORM配置里,甚至是一个被遗忘的开发连接。锁表问题从来不是DDL命令的问题,而是谁在读表时忘了收手。


















