SELECT FOR UPDATE本身不阻塞读,但因未提交事务长期持有S MDL锁,导致ALTER TABLE等需X MDL的操作被阻塞,进而使后续所有读写操作排队等待。

SELECT FOR UPDATE 本身不直接阻塞读,但会通过元数据锁(MDL)间接卡死所有后续读写操作——根本原因不是它锁了行,而是它让事务长期开着,死死攥着 S MDL 不放。
为什么 ALTER TABLE 会被卡住?
ALTER TABLE 必须拿到表级排他元数据锁(X MDL),而任何未提交事务中执行的 SELECT ... FOR UPDATE 都会持续持有该表的共享元数据锁(S MDL)。S 和 X 互斥,所以 DDL 只能排队等。
-
SELECT ... FOR UPDATE在显式事务里才生效(BEGIN或START TRANSACTION),自动提交模式下锁秒放,不影响 DDL - 哪怕只查
WHERE id = 1这一行,只要事务没COMMIT或ROLLBACK,S MDL 就一直存在 - 应用层处理慢(比如 PHP 查完没立刻
UPDATE)、网络超时、异常未回滚,都会让事务“悬停”,S MDL 白占着不放
为什么普通 SELECT 也会被堵?
不是 SELECT ... FOR UPDATE 直接锁了读,而是它拖住了 DDL;DDL 卡着不动,又会阻塞后续所有需要同级别 MDL 的操作——包括其他事务的普通 SELECT、INSERT、UPDATE,因为它们都得先抢到 S MDL 才能继续。
- 在
SHOW PROCESSLIST里看到状态是Sleep或Waiting for table metadata lock,大概率是空闲事务没结束 - MySQL 5.7+ 可用
SELECT * FROM sys.innodb_lock_waits快速定位谁在等、谁在挡 - 别只盯着“锁行”看:行锁(X lock)和元数据锁(S MDL)是两套机制,前者管数据修改,后者管结构变更和并发访问控制
怎么确认是不是它惹的祸?
三步定位,不靠猜:
- 查正在等待的 DDL:
SELECT * FROM information_schema.PROCESSLIST WHERE COMMAND = 'Query' AND STATE = 'Waiting for table metadata lock' - 查谁拿了 S MDL 不放:
SELECT blocking_trx_id, blocked_trx_id FROM performance_schema.data_lock_waits(需开启performance_schema) - 更直接:
SELECT * FROM sys.innodb_lock_waits,或手动关联PROCESSLIST看ID、USER、INFO字段
锁升级导致全表扫描才是真杀手
当 SELECT ... FOR UPDATE 的 WHERE 条件没走索引(比如 status = '' 且字段无索引、name LIKE '%abc'、隐式类型转换),InnoDB 会退化为全表扫描,并对所有扫描行加 X 锁。在 REPEATABLE READ 下,这基本等于锁住整个聚簇索引——也就是事实上的表锁。
- 用
EXPLAIN FORMAT=JSON检查key和key_locks字段,确认是否命中索引 - 复合查询务必建联合索引,不能只依赖单列索引
- 禁止在事务中执行带前导通配符的
LIKE查询,它必然无法使用索引
最常被忽略的一点:问题不在语句本身多危险,而在事务生命周期失控。一个没 COMMIT 的 SELECT ... FOR UPDATE,比十个写错的 UPDATE 更容易让整张表瘫痪几个小时。


















