select语句一定会加mdl_shared_read锁,且释放时机取决于执行上下文:自动提交下执行完即释放,显式事务中持续到commit/rollback,mysqldump或information_schema查询也会触发并延长持锁时间。

SELECT语句一定会加MDL锁,但释放时机很关键
是的,SELECT 语句执行时一定会申请 MDL_SHARED_READ 锁(共享读锁),无论是否在事务中、是否带 WHERE、是否只查一行或全表扫描。这不是可选行为,而是 MySQL Server 层为保护表结构一致性强制施加的机制。真正影响业务的是锁的持有时间——它不随语句结束立即释放,而取决于执行上下文:
- 自动提交模式下(默认):
SELECT执行完即释放 MDL 锁 - 显式事务中(
BEGIN后):SELECT持有 MDL 锁直到COMMIT或ROLLBACK -
mysqldump --single-transaction:对每个 dump 表加MDL_SHARED_READ,持续到整个 dump 完成 - 查询
INFORMATION_SCHEMA(如SELECT * FROM COLUMNS):也触发短暂但关键的 MDL 请求,可能卡住 DDL
为什么简单 SELECT 会阻塞 ALTER TABLE?
因为 ALTER TABLE 需要 MDL_EXCLUSIVE(排他锁),而 MDL_SHARED_READ 和 MDL_EXCLUSIVE 是互斥的。只要有一个长事务里执行过 SELECT,该表的 MDL 读锁就一直挂着,后续所有 DDL 都得排队等待。常见误判是以为“只是查一下,没写,不该阻塞”,但问题不在数据行,而在表定义本身。
- 典型链路:
BEGIN→SELECT * FROM orders→ 卡住不提交 →ALTER TABLE orders ADD COLUMN x进入Waiting for table metadata lock - 即使
SELECT id FROM t LIMIT 1在事务里,也会持锁 - 存储过程中调用
SELECT,若过程内隐式开启事务且未COMMIT,锁同样不会释放
如何快速定位谁在持 MDL 读锁?
不能只看 SHOW PROCESSLIST,因为持锁的会话状态仍是 Sleep 或 Query,不显示“Locked”。必须结合系统表交叉验证:
- 查当前活跃事务:
SELECT * FROM information_schema.INNODB_TRX WHERE trx_state = 'RUNNING' - 查元数据锁等待关系:
SELECT * FROM performance_schema.metadata_locks WHERE OBJECT_SCHEMA = 'db_name' AND OBJECT_NAME = 'table_name' - 确认锁类型:
LOCK_TYPE为SHARED_READ表示读锁;LOCK_STATUS为PENDING表示正在等锁 - 关联线程:
OWNER_THREAD_ID可连到performance_schema.threads查 SQL 文本
容易被忽略的三个坑
很多线上故障不是因为不懂 MDL,而是踩在了这些边界细节上:
-
SET GLOBAL lock_wait_timeout = 5只控制“等待锁”的超时,对已持有的 MDL 锁完全无效——源头事务不结束,新 DDL 还是会超时报错 -
REPEATABLE READ下第一次SELECT就加 MDL 锁,之后同一事务内再查同表,不会重复申请,但锁也不会提前释放 - 临时表(
CREATE TEMPORARY TABLE)也会触发 MDL,虽然作用域限于会话,但在存储过程中混用时容易引发意外锁升级











