MySQL执行DDL操作时怎样处理MDL锁阻塞其他查询

作者:袖梨 2026-06-19
DDL卡住时SELECT挂起是因MDL锁队列机制:ALTER需MDL_EXCLUSIVE锁但被长事务的MDL_SHARED_READ占用,导致后续所有请求(含SELECT)排队等待;ALGORITHM=INPLACE和LOCK=NONE不规避MDL锁等待,NO_WAIT可实现快速失败。

DDL卡住时,为什么SELECT也跟着挂起?

不是DDL本身阻塞了SELECT,而是MySQL的MDL锁队列机制导致的连锁等待。当一个ALTER TABLE需要MDL_EXCLUSIVE锁但拿不到(比如被长事务的MDL_SHARED_READ锁占着),它就会排在锁队列里;后续所有对该表的新请求(包括SELECT)都必须等这个排队中的MDL_EXCLUSIVE锁“先过”,哪怕它们只需要MDL_SHARED_READ

常见现象:show processlist里看到多个线程状态都是Waiting for table metadata lock,但真正卡住源头的可能只是一个没提交的BEGIN; SELECT ...

  • 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照样卡在第一步,后面所有查询照常排队。

  • 真正决定是否阻塞的是“能否立刻拿到MDL锁”,而不是用了什么算法
  • 加字段、建索引这类操作,在InnoDB上多数支持INPLACE,但只要表上有未提交事务,就逃不开MDL等待
  • MyISAM表不支持INPLACE,任何ALTER都全程持有MDL_EXCLUSIVE锁,阻塞更彻底

如何让DDL失败快、不拖垮业务?

NO_WAITWAIT N显式控制等待行为,避免无限期挂起。

阿里云RDS和MySQL 8.0+支持在DDL语句末尾加NO_WAITWAIT 1(单位秒)。例如:ALTER TABLE t ADD COLUMN c INT NO_WAIT。一旦无法立即获取MDL_EXCLUSIVE锁,立刻报错ERROR 3572 (HY000): Statement aborted: Lock wait timeout exceeded,而不是让线程一直挂着。

  • NO_WAIT:不等,直接失败
  • WAIT 2:最多等2秒,超时即报错
  • 该语法适用于ALTERRENAMEDROPTRUNCATECREATE INDEX等绝大多数DDL
  • 注意:这不能解决长事务问题本身,只是把“阻塞”变成“快速失败”,便于上层重试或告警

查不出谁在占MDL锁?试试这几个关键视图

别只看show processlist,它只显示当前活跃连接,而真正持锁的可能是已Sleep但事务未提交的连接。

优先查这三个地方:

  • 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 = RUNNINGtrx_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在那里,默默卡住所有DDL——这种连接在processlist里看起来完全正常,但INNODB_TRX里能看到它。

相关文章

精彩推荐