SELECT ... FOR UPDATE会卡住是因为它属于当前读,绕过MVCC快照机制,必须加锁并等待最新已提交数据;而普通SELECT是快照读,基于MVCC不加锁、不阻塞。

为什么SELECT ... FOR UPDATE会卡住,而普通SELECT不会
因为MVCC只对“快照读”(普通SELECT)生效,对“当前读”(SELECT ... FOR UPDATE、UPDATE、DELETE)无效。当前读必须看到最新已提交数据,并加锁,所以哪怕事务隔离级别是REPEATABLE READ,它仍会去争抢行锁或间隙锁。
常见错误现象:SELECT ... FOR UPDATE一直不返回、状态显示Updating或Locked、SHOW PROCESSLIST里出现Waiting for table metadata lock(注意:这其实是MDL锁,和MVCC无关,但常被误认为是MVCC问题)。
实操建议:
- 先确认是否真在用MVCC机制:查
SELECT @@transaction_isolation,确保不是READ UNCOMMITTED或SERIALIZABLE - 如果只是普通
SELECT卡住,基本和MVCC无关——它本不该加锁,卡住大概率是表级锁(如DDL执行中)或MDL锁 -
SELECT ... FOR UPDATE卡住,本质是锁等待,不是MVCC失效,而是它主动放弃了MVCC的快照能力,进入当前读路径
如何用performance_schema.data_locks定位MVCC外的阻塞源
MySQL 8.0+ 已废弃INNODB_LOCKS,必须用performance_schema.data_locks查谁持有了哪一行的X锁或S锁。MVCC本身不产生锁,但它让多个事务能“各看各的版本”,一旦有事务打破这个边界(比如加了FOR UPDATE),锁就暴露出来了。
实操建议:
- 运行
SELECT * FROM performance_schema.data_locks WHERE OBJECT_SCHEMA = 'your_db' AND OBJECT_NAME = 'your_table',重点看LOCK_TRX_ID和LOCK_MODE(如X,REC_NOT_GAP表示行排他锁) - 把
LOCK_TRX_ID拿去查performance_schema.data_lock_waits,就能看到谁在等谁 - 别漏掉
LOCK_TYPE = 'TABLE'的记录——这可能是MDL锁,和MVCC完全无关,但表现类似“只读被堵” - 如果
data_locks里没看到你的表,说明没人在持锁,那卡住原因大概率是磁盘满、只读模式、或连接池配置过小
read_only和super_read_only怎么干扰MVCC的“读”行为
MVCC的快照读不受read_only影响,但SELECT ... FOR UPDATE这类当前读会被直接拒绝,报错ERROR 1792 (HY000): Cannot execute statement in a READ ONLY transaction。这不是MVCC机制出问题,而是MySQL在语句解析阶段就拦截了写意图。
实操建议:
- 立刻执行
SELECT @@read_only, @@super_read_only,两个都必须为0才能执行当前读 - 云厂商RDS常静默开启
super_read_only=ON,改完配置文件不生效,得进控制台关开关 - 主从架构下,从库默认
read_only=ON,但开发环境连错从库做调试时,容易误以为是代码问题 - 别指望
SET SESSION read_only = OFF——这个变量是只读的,只能由有SUPER权限用户用SET GLOBAL改
innodb_lock_wait_timeout设太小会让MVCC排查变困难
这个参数控制锁等待超时时间,默认50秒。设得太小(比如5秒),会导致SELECT ... FOR UPDATE频繁报Lock wait timeout exceeded,掩盖真实阻塞源头——你还没来得及查data_locks,事务就被自动回滚了。
实操建议:
- 排查期间临时调大:执行
SET SESSION innodb_lock_wait_timeout = 300(5分钟),给足时间抓现场 - 开发/测试环境可设为
5,快速暴露锁冲突,但生产环境别长期这么干 - 注意:这个参数只影响InnoDB行锁等待,不影响MDL锁等待(MDL锁超时由
lock_wait_timeout控制,单位秒,需GLOBAL权限修改) - 真正难排查的,是那种“持锁事务没提交也没rollback”,只在
INNODB_TRX里显示RUNNING且trx_started时间异常久远——这时候得结合应用日志找对应业务逻辑
最易被忽略的是:MVCC本身不阻塞任何读,所有“只读被堵”的表象,背后都是当前读、MDL锁、只读模式或磁盘问题在起作用。盯着MVCC原理本身去查阻塞,方向就错了。


















