create or replace procedure会隐式加mdl_exclusive锁,阻塞所有对该数据库中表的并发读写及ddl操作,且锁生命周期独立于应用事务,失败或高并发重建易引发连锁等待。

根本原因不是“重建”本身,而是重建过程触发的隐式DDL与事务边界失控
CREATE OR REPLACE PROCEDURE 会隐式加 MDL 排他锁
MySQL 对存储过程的元数据(如 mysql.proc 表)也施加了元数据锁保护。执行 CREATE OR REPLACE PROCEDURE 或 DROP PROCEDURE 时,MySQL 必须获取该过程所关联数据库的 MDL_EXCLUSIVE 锁——哪怕只是覆盖一个已存在的过程。这个锁会阻塞所有正在访问同一数据库中任何表的并发操作,包括:
- 其他会话正在执行的
SELECT、INSERT等语句(它们需先获取对应表的 MDL_SHARED_READ/WRITE) - 正在进行的
ALTER TABLE、CREATE INDEX等 DDL - 甚至其他会话里刚调用完但事务尚未提交的存储过程(因事务未结束,其涉及表的 MDL 共享锁仍被持有)
关键点在于:CREATE OR REPLACE PROCEDURE 是一个原子性 DDL 操作,它不参与你应用层的事务控制;它的锁生命周期独立且严格,必须等整个语句执行完成、日志刷盘、缓存刷新后才释放。
频繁重建放大锁冲突窗口,尤其在低版本或高并发下
MySQL 5.7 中,mysql.proc 表使用 MyISAM 引擎(只读系统表),但其访问仍受 MDL 控制;而 8.0+ 改为 InnoDB 后虽支持事务,但 CREATE PROCEDURE 仍是不可回滚的 DDL。频繁重建会导致:
- 多个重建请求排队等待获取数据库级 MDL_EXCLUSIVE,形成“重建风暴”
- 若某次重建因语法错误失败,MySQL 仍可能短暂持有部分锁(尤其在解析阶段卡住时),后续请求继续排队
- 应用若在连接池中复用连接并开启事务后执行重建,则该连接的事务会意外延长 MDL 持有时间(例如:BEGIN → CREATE OR REPLACE PROCEDURE → 出错未 COMMIT/ROLLBACK)
- 监控工具或运维脚本定时调用重建(如部署钩子),与业务高峰重叠时极易触发连锁等待
为什么 SHOW PROCESSLIST 看不到源头,却满屏 Waiting for table metadata lock
真正持锁的会话往往不是那个正在执行 CREATE OR REPLACE PROCEDURE 的连接,而是它“唤醒”的下游阻塞链:
- 会话 A 执行
CREATE OR REPLACE PROCEDURE p1→ 拿到db1的 MDL_EXCLUSIVE - 会话 B 此时正执行
CALL p2(),而p2内部访问了db1.t1→ 需要t1的 MDL_SHARED_READ,但被 A 的排他锁挡住 → 状态变为Waiting for table metadata lock - 会话 C 紧接着执行
SELECT * FROM db1.t1→ 同样卡在相同等待状态(MDL 排队规则:后到的共享锁必须等前面所有锁释放)
此时 SHOW PROCESSLIST 里 A 可能已结束(显示 Query OK),但 B 和 C 还在等;而真正的问题源头其实是 A 刚刚释放锁的瞬间,B 因内部逻辑慢或事务未提交,又把锁“接力”持有了——这正是排查中最容易漏掉的一环。
复杂点在于:MDL 锁的传递不体现在 INNODB_TRX,也不出现在 performance_schema.data_locks(那是行锁)。你得交叉比对 events_statements_current(看谁刚执行过 DDL)、INNODB_TRX(找长期 RUNNING 的事务)、以及 processlist 的 Time 和 State 字段,才能串起完整阻塞链。单纯杀掉显示 Waiting 的会话,治标不治本。











