先澄清一个常见误区:执行DDL时SELECT跟着挂起,并不是DDL本身有多霸道,而是MySQL的MDL锁队列机制在作祟。当一个ALTER TABLE试图获取MDL_EXCLUSIVE锁却迟迟拿不到——比如被某个长事务的MDL_SHARED_READ锁牢牢占着——它就会乖乖排在锁队列里。这时候,任何后续发往该表的新请求,哪怕只是一个简单的SELECT,也都必须等这个正在排队的MDL_EXCLUSIVE锁先“过了这一关”。这就是所谓的连锁等待,也是导致MySQL DDL阻塞其他查询的典型现象。
现实中你看到的现象往往是这样的:show processlist里一大票线程的状态都是Waiting for table metadata lock,但真正卡住源头的,可能只是一个还没来得及提交的BEGIN; SELECT ...。相当隐蔽,这也是MDL锁排查中常见的隐藏坑。
- MySQL 5.7默认并不保证“先到先得”——排队中的
SELECT有时反而比ALTER更早拿到锁,尤其是当ALTER是online且加锁时间极短的时候。 - MySQL 8.0在调度逻辑上做了优化,确保
ALTER在队列中不会被“饿死”,但即便如此,它依然会拖慢后续所有请求的响应。 - 另一个容易被忽略的场景:
mysqldump --single-transaction虽然每个表的MDL_SHARED_READ锁持有时长很短,但在高并发DDL下,仍然可能成为一个计划外的瓶颈。

ALGORITHM=INPLACE 和 LOCK=NONE 真的不阻塞吗?
这个问题得说清楚。这两个参数确实影响DML操作是否能并发执行,但它们跟MDL锁的获取与等待完全不是一回事。
ALGORITHM=INPLACE的意思是DDL过程不涉及拷表操作,但它在准备阶段和提交阶段仍然需要短暂地获取MDL_EXCLUSIVE锁。同样,LOCK=NONE表示“不阻塞DML”,但请注意——它没有跳过MDL锁等待这一步。换句话说,如果此时已经有一个长事务持着MDL_SHARED_READ,DDL照样卡在第一步,后续所有查询也会照常排起长队。这就是为什么即使使用了INPLACE算法,ALTER TABLE仍然可能造成阻塞。
- 真正决定是否发生阻塞的,是DDL能否“立刻拿到MDL锁”,而不是用了什么算法。
- 加字段、建索引这类操作,在InnoDB上通常都支持
INPLACE,但只要表上还挂着未提交的事务,MDL等待就在所难免。 - 至于MyISAM表,它不支持
INPLACE,任何ALTER操作都全程持有MDL_EXCLUSIVE锁,阻塞效果只会更彻底。
如何让DDL失败快、不拖垮业务?
关键在于用NO_WAIT或WAIT N来显式控制DDL的等待行为,避免无限期挂起,从而保护其他查询不被长时间阻塞。
阿里云RDS和MySQL 8.0+都支持在DDL语句末尾加上NO_WAIT或WAIT 1(单位秒)。举个例子:ALTER TABLE t ADD COLUMN c INT NO_WAIT。一旦无法立即获取MDL_EXCLUSIVE锁,DDL会立刻报错ERROR 3572 (HY000): Statement aborted: Lock wait timeout exceeded,而不是让线程一直悬在那里。这样就能让DDL快速失败,便于业务层及时重试或触发告警。
NO_WAIT:不等,直接失败。WAIT 2:最多等2秒,超时即报错。- 这个语法适用于
ALTER、RENAME、DROP、TRUNCATE、CREATE INDEX等绝大多数DDL。 - 需要注意的是,这并不能解决长事务本身的问题,它只是把“阻塞”变成了“快速失败”,便于上层及时重试或触发告警。
查不出谁在占MDL锁?试试这几个关键视图
别只盯着show processlist看,它只显示当前活跃的连接,而真正持锁的可能是一个早已Sleep但事务还未提交的连接。要定位MDL锁的源头,需要深入系统表。
优先排查这三个地方:
SELECT * FROM performance_schema.threads WHERE PROCESSLIST_STATE = 'Sleep' AND PROCESSLIST_INFO IS NULL—— 找出那些看似空闲但可能挂着一堆事务的连接。SELECT * FROM information_schema.INNODB_TRX WHERE TIME_TO_SEC(NOW() - trx_started) > 60—— 查运行超过1分钟的事务,重点关注trx_state = RUNNING且trx_started非常老的那些。SELECT OBJECT_SCHEMA, OBJECT_NAME, LOCK_TYPE, LOCK_DURATION, LOCK_STATUS FROM performance_schema.metadata_locks WHERE OWNER_THREAD_ID IN (SELECT THREAD_ID FROM performance_schema.threads WHERE PROCESSLIST_STATE = 'Sleep')—— 直接看哪些表被哪些Sleep线程锁着。
最常见的隐藏坑是:某个应用连接池里的连接,在执行完SELECT后既没有COMMIT也没有ROLLBACK,就一直处于Sleep状态。这种连接在processlist里看起来完全正常,但INNODB_TRX里却能清清楚楚地看到它默默卡住所有DDL。掌握这些视图,就能快速定位并解除MDL锁阻塞问题。
