waiting for table metadata lock 是mdl锁等待结果,真正阻塞源是未提交事务(含存储过程内隐式事务);应查innodb_trx定位running事务,结合events_statements_current和show engine innodb status确认持锁语句与表。

Waiting for table metadata lock 出现在存储过程并发调用时,根本不是存储过程本身“写得有问题”,而是 MySQL 5.7 的元数据锁(MDL)机制在事务边界模糊、语句未及时释放锁的场景下被意外拉长所致。
为什么调用存储过程会触发 MDL 锁?
只要存储过程中包含任何对表的访问(哪怕只是 SELECT),MySQL 就会在执行开始时自动申请该表的 MDL 读锁;如果过程里有 INSERT/UPDATE/DELETE 或隐式 DDL(如临时表创建),还可能升级为更重的锁。关键点在于:MDL 锁生命周期绑定事务,而非单条语句。
- 显式开启事务(
BEGIN)后调用存储过程 → 整个事务结束前,所有涉及表的 MDL 锁都不会释放 - 存储过程内部含
START TRANSACTION但没配对COMMIT/ROLLBACK→ 锁卡在过程内,外部调用者无法控制释放时机 - 过程里执行了失败的语句(如字段不存在的
SELECT)→ 错误语句仍会持有 MDL 锁,直到事务显式结束(ROLLBACK或连接断开)
并发调用时锁等待的真实链路
典型阻塞链不是“两个存储过程互相等”,而是:一个慢过程长期持锁 → 后续 DDL(如 ALTER TABLE)卡住 → 连带阻塞所有新进来的 DML 查询。
- 会话 A 调用
proc_update_stats(),内部含长耗时逻辑(如循环更新 +SLEEP(10)),且未提交事务 - 会话 B 执行
ALTER TABLE orders ADD COLUMN processed TINYINT→ 等待orders表的 MDL 写锁 - 会话 C 执行
SELECT * FROM orders LIMIT 1→ 因会话 B 卡在 MDL 等待中,也进入Waiting for table metadata lock
此时 SHOW PROCESSLIST 里会看到多个会话状态都是 Waiting for table metadata lock,但真正源头只有一条未结束的事务。
lock_wait_timeout 不是万能解药
设 SET GLOBAL lock_wait_timeout = 5 确实能让 DDL 在 5 秒后报错退出,避免无限等待,但它不解决锁源头问题,只掩盖症状:
- DDL 报错
ERROR 1205 (HY000): Lock wait timeout exceeded后,应用若无重试或降级逻辑,业务直接失败 - 原持有锁的会话依然存在,后续所有新 DDL 和依赖该表的查询仍会重复触发超时
- 该参数对已持有的锁无效——它只约束“等待锁”的时间,不干预“持有锁”的时长
排查必须盯住这三处
别只看 SHOW PROCESSLIST,真正线索藏在三个地方:
-
SELECT * FROM information_schema.INNODB_TRX WHERE trx_state = 'RUNNING'→ 找出未提交事务的trx_mysql_thread_id -
SELECT * FROM performance_schema.events_statements_current WHERE thread_id = ?→ 查该线程最后执行的语句,确认是否失败语句残留锁 -
SHOW ENGINE INNODB STATUS\G中的TRANSACTIONS部分 → 看mysql tables in use和locked tables列,定位具体哪张表被锁死
最常被忽略的是:存储过程里用 DECLARE CONTINUE HANDLER 捕获了异常却忘了 ROLLBACK,导致事务静默悬挂——这种“安静的锁”最难发现,也最致命。











