select被卡住通常因等待mdl锁或全局读锁:前者由长事务或慢ddl引发,表现为waiting for table metadata lock;后者由flush tables with read lock导致,状态为waiting for global read lock。

SELECT 被卡住,先看是不是在等 MDL 锁
普通 SELECT 不加行锁,但会申请元数据锁(MDL),而 DDL 操作(如 ALTER TABLE、DROP INDEX)会持有更强的 MDL 锁。一旦有长事务或慢 DDL 正在执行,后续所有对同一张表的 SELECT 都会被阻塞——现象是查询“卡住不动”,SHOW PROCESSLIST 显示状态为 Waiting for table metadata lock。
常见触发场景:
- 有人在后台执行
ALTER TABLE t ADD COLUMN x INT,没加ALGORITHM=INPLACE - 一个事务里执行了
SELECT后迟迟不提交,期间别人尝试TRUNCATE t -
mysqldump --single-transaction备份时,恰好遇到大表ALTER,dump 进程自己被反向阻塞
验证方式:运行 SELECT * FROM performance_schema.metadata_locks WHERE OBJECT_SCHEMA = 'db_name' AND OBJECT_NAME = 't';,重点关注 LOCK_STATUS = 'PENDING' 和 LOCK_TYPE 级别(EXCLUSIVE 会阻塞所有 SELECT)。
FLUSH TABLES WITH READ LOCK 导致全库只读阻塞
这不是事务隔离级别或行锁的问题,而是人为施加的全局读锁。一旦执行 FLUSH TABLES WITH READ LOCK,所有后续的 SELECT(包括无事务的简单查询)都会挂起,直到锁被显式释放(UNLOCK TABLES)或连接断开。
这个命令极少在业务代码中出现,但容易在以下地方误用:
- 运维手动备份脚本里没配超时或异常退出逻辑
- 某些旧版迁移工具(如早期 pt-table-checksum)在特定模式下自动加锁
- DBA 在主库上执行后忘记解锁,从库 IO 线程也同步被卡住(因为
FLUSH TABLES WITH READ LOCK是 binlog 记录事件)
判断方法:SHOW PROCESSLIST 中能看到一个状态为 Waiting for global read lock 的线程,且其 Command 是 Query,Info 字段含 FLUSH 或为空;同时其他所有新进来的 SELECT 状态都是 Waiting for global read lock。
为什么 EXPLAIN 看不出 MDL 锁问题
EXPLAIN 只分析查询执行计划,不反映锁等待状态。即使一条 SELECT 因 MDL 被卡住几十秒,EXPLAIN 仍会立刻返回“正常”的执行计划(type=const、key=PRIMARY 等),让人误以为 SQL 没问题。
真正该查的是运行时视图:
-
performance_schema.threads+performance_schema.events_waits_current:找event_name LIKE '%metadata%'的等待 -
information_schema.INNODB_TRX:确认有没有长事务占着表不让别人加 MDL -
SHOW OPEN TABLES WHERE In_use > 0:快速识别哪些表正被强锁占用(In_use表示有活跃的 MDL)
注意:innodb_lock_wait_timeout 对 MDL 锁无效——它只控制行锁等待超时,MDL 等待超时由 lock_wait_timeout(默认 31536000 秒,即一年)控制,所以卡住后不会报错,只会一直挂。
SELECT 被阻塞但没看到明显锁源?检查隐藏的 AUTO_COMMIT=0 会话
有些客户端(比如 MySQL CLI 默认、某些 JDBC 驱动配置)开启事务后不显式 COMMIT,导致一个空闲连接长期持有 MDL(哪怕只执行过一次 SELECT)。这种会话在 SHOW PROCESSLIST 里状态可能是 Sleep,但 COMMAND 是 Sleep、TIME 很大、INFO 为空——它其实仍在事务中,阻止了 DDL,反过来又让新 SELECT 等它释放 MDL。
排查要点:
- 查
SELECT * FROM information_schema.INNODB_TRX WHERE TIME_TO_SEC(NOW()) - TIME_TO_SEC(TRX_STARTED) > 60;找出运行超 1 分钟的事务 - 结合
performance_schema.threads查这些事务对应的PROCESSLIST_ID,再看它的CONNECTION_TYPE和客户端 IP - 特别留意来自监控探针、定时任务脚本、或开发本地测试环境的长空闲连接
这类问题最难发现,因为阻塞源和被阻塞方不在同一时间窗口活跃,且没有 ERROR 日志——它靠的是“事务未结束”这个隐性状态持续持有锁。











