mysql如何处理SQL执行过程中的Metadata Lock_mysql元数据加锁

2次阅读

show processlist 中出现“waiting for table metadata lock”是因为元数据锁(mdl)阻塞:长事务或未提交ddl持有表的mdl,导致后续所有访问该表的操作(包括select)排队等待。

mysql如何处理SQL执行过程中的Metadata Lock_mysql元数据加锁

为什么 SHOW PROCESSLIST 里总卡着 Waiting for table metadata lock

这是 mysql 5.5+ 引入元数据锁(MDL)后最典型的阻塞现象:一个长事务或未提交的 DDL 正拿着表的 MDL,其他所有想访问该表的语句(哪怕只是 SELECT)都得排队等。不是锁行、也不是锁表,是锁“表结构定义”本身。

关键点在于:MDL 是语句执行前就加上的,且会一直持有到事务结束(即使语句早已执行完)。所以一个 BEGIN; SELECT * FROM t1; 不提交,后续对 t1 的任何操作都会被拦住。

  • 常见诱因:ALTER TABLEDROP TABLERENAME TABLE 等 DDL;长时间运行的 SELECT 或未提交的 UPDATE
  • 注意:SELECT 也会加 MDL(共享锁),但和 DDL 的排他锁冲突 —— 这就是只读查询也被卡住的原因
  • DDL 操作默认在 autocommit=1 下执行,看似“单条”,实则内部仍需获取排他 MDL,期间会等所有已有共享锁释放

怎么快速定位谁在 hold 住 MDL

SHOW PROCESSLIST 只能看到“等待者”,看不到“持有者”。必须查 performance_schema 的锁视图(MySQL 5.6+ 默认开启):

SELECT OBJECT_SCHEMA, OBJECT_NAME, LOCK_TYPE, LOCK_DURATION, LOCK_STATUS, OWNER_THREAD_ID FROM performance_schema.metadata_locks WHERE OBJECT_SCHEMA = 'your_db' AND OBJECT_NAME = 'your_table';

重点关注 LOCK_STATUS = 'PENDING'(等待中)和 LOCK_STATUS = 'GRANTED'(已持有)的记录,再用 OWNER_THREAD_IDperformance_schema.threads 查对应线程的 SQL 和状态。

  • 如果 performance_schema 关闭了,只能靠 SHOW ENGINE INNODB STATUSG 翻 TRANSACTIONS 部分,但看不到 MDL 细节,仅能推测
  • information_schema.INNODB_TRX 能看到活跃事务,但无法直接关联到 MDL 持有者 —— 别指望它直接给出答案
  • 别依赖 PROCESSLISTTime 字段判断“谁更老”,因为等待时间不等于持有时间

KILL 哪个线程才真正解围

必须 KILL 持有排他锁(或长时间占着共享锁不释放)的那个线程,而不是一在等的线程 —— 杀错对象只会让等待队列更长。

  • 优先杀 DDL 线程:比如正在执行 ALTER TABLE t1 ADD column x int 的线程,它拿的是排他 MDL,影响最大
  • 其次考虑长事务中的第一个语句线程:例如一个跑了 20 分钟的 BEGIN; UPDATE t1 ...;,哪怕当前没在执行,MDL 仍被绑定在事务上
  • 不要杀 Waiting for table metadata lock 状态的线程 —— 它们没锁,杀了也没用,还会重连重试,继续排队
  • KILL 后 DDL 会回滚(如 ALTER),事务会中断,业务侧需处理失败逻辑

怎么避免线上频繁触发 MDL 等待

根本思路是缩短 MDL 持有时长 + 隔离高风险操作。没有银弹,但有几个硬约束必须遵守:

  • DDL 操作务必在低峰期执行,且提前确认没有长事务在操作目标表(用 SELECT * FROM information_schema.INNODB_TRX WHERE TIME_TO_SEC(NOW() - trx_started) > 60; 扫描)
  • 应用层禁止在事务里执行无关的 SELECT,尤其是跨表、带子查询的“大查询”,它们会把 MDL 锁住整个事务周期
  • MySQL 5.7+ 可开启 lock_wait_timeout(默认 31536000 秒),但它是针对锁等待超时,对 MDL 无效;真正有用的是 wait_timeoutinteractive_timeout 控制空闲连接自动断开
  • 对于大表 DDL,优先用 pt-online-schema-change 或 MySQL 8.0 的 ALGORITHM=INSTANT(仅限部分变更),它们能绕过传统 MDL 排他锁

MDL 的设计本意是保证 DDL 和 DML 的强一致性,但它把“安全”变成了“脆弱”。真正难的不是查锁,而是让开发和 dba 对“事务边界”和“DDL 影响面”有同一认知 —— 很多问题,其实发生在 BEGIN 那一行之前。

text=ZqhQzanResources