JOIN 查询本身不导致MDL阻塞,但若其处于未提交事务、连接未释放或autocommit=0状态下,会持续持有共享元数据锁(S-MDL),从而阻塞ALTER TABLE等DDL操作;定位需查INNODB_TRX与schema_table_lock_waits,KILL前须确认事务状态与影响。

JOIN 本身不会直接导致元数据锁(MDL)阻塞,但执行 JOIN 的会话若长期持有事务或连接不释放,就会让 MDL 一直挂在表上,卡住后续 DDL。核心问题是“谁在占着锁不放”,而不是 JOIN 写法有问题。
为什么 JOIN 查询会拖住 ALTER TABLE?
MySQL 对所有 DML(包括 SELECT JOIN)都会在语句开始时加共享元数据锁(S-MDL),目的是防止别人在你读表结构期间把表删了或改了列。这个锁通常语句一结束就释放——但如果你显式开了事务(BEGIN)、或者应用没关连接、或者 ORM 自动设了 autocommit=0,那这个 S-MDL 就会一直挂着,直到事务提交或连接断开。
- 常见错误现象:
ALTER TABLE t ADD COLUMN x INT卡住,SHOW PROCESSLIST显示状态是Waiting for table metadata lock - 真实阻塞者往往是一条看似无害的
SELECT * FROM orders JOIN users ON ...,但它属于一个没提交的事务 - 注意:即使这条 SELECT 没带
FOR UPDATE,只要它在长事务里,MDL 就不释放
怎么快速定位那个“赖着不走”的 JOIN 会话?
别只看 SHOW PROCESSLIST,它不显示事务上下文。必须查 information_schema.INNODB_TRX 和 sys.schema_table_lock_waits(MySQL 8.0+)才能看清因果链。
- 先筛出运行超 60 秒的事务:
SELECT * FROM information_schema.INNODB_TRX WHERE TIME_TO_SEC(NOW()) - TIME_TO_SEC(TRX_STARTED) > 60 - 再连查
performance_schema.threads拿到它的PROCESSLIST_INFO,确认是不是一条 JOIN 查询卡在那儿 - 直接跑:
SELECT * FROM sys.schema_table_lock_waits\G,重点看BLOCKING_TRX_ID和BLOCKING_PID—— 后者就是能KILL的线程 ID - 如果
BLOCKING_SQL是空的,说明阻塞者是隐式事务(比如只执行了INSERT没COMMIT)
KILL 之前必须确认的三件事
KILL pid 能释放 MDL,但误杀可能引发应用报错或数据不一致。动手前务必交叉验证:
- 用
SELECT * FROM performance_schema.threads WHERE PROCESSLIST_ID = <code>pid确认PROCESSLIST_INFO真的是那条 JOIN,且已卡住 5 分钟以上 - 检查该事务的
TRX_ROWS_MODIFIED:如果是 0,大概率是应用层没提交,不是正在跑大查询 - 绝对不要用
KILL QUERY <code>pid—— 它只中断当前语句,事务还在,MDL 依然持有
从源头减少 JOIN 引发的 MDL 风险
靠事后 KILL 是救火,真正要堵住漏洞得改习惯:
- 所有业务代码里,含 JOIN 的只读查询必须设
autocommit=1,或显式用START TRANSACTION READ ONLY(MySQL 5.6+)并立刻COMMIT - ORM 配置里禁用全局
autocommit=0,避免一条查询就把整个连接拖进事务池 - DBA 执行 DDL 前,先跑
SELECT COUNT(*) FROM information_schema.INNODB_TRX WHERE TRX_STATE = 'RUNNING',大于 0 就暂停操作 - 监控告警项应包含:持续 30 秒以上的活跃事务数、
sys.schema_table_lock_waits非空记录数
最常被忽略的一点:很多 JOIN 查询本身很快,但因为复用了一个长期存活的数据库连接(比如连接池里 idle 超过 10 分钟的连接),而该连接上次事务没清理干净,导致 MDL 悬挂。所以问题不在 SQL,而在连接生命周期管理。

















