S锁持续占用导致X锁无限等待即写饥饿:多个事务可同时持S锁,阻塞所有X锁请求;长事务、隐式锁、高并发只读等场景易引发,MVCC不缓解显式S锁竞争,需通过缩短事务、读写分离、降级隔离级别等手段缓解。

Shared锁持续占用导致Exclusive锁无限等待
MySQL的S锁(LOCK IN SHARE MODE 或隐式读锁)不互斥,多个事务可同时持有。只要有一个S锁未释放,任何X锁请求(SELECT FOR UPDATE、UPDATE、DELETE)就会被阻塞。在读多写少且读操作耗时长的场景下,S锁可能长期堆积,X锁线程只能排队——这不是死锁,而是典型的“写饥饿”。
常见诱因包括:
- 长事务中执行了
SELECT ... LOCK IN SHARE MODE但迟迟不提交 - 应用层未显式控制事务边界,导致自动提交关闭后S锁滞留
- 高并发API频繁调用只读接口(如配置查询),且底层复用了同一连接池中的长连接
InnoDB行锁 + MVCC 并不能缓解S锁饥饿
MVCC确实让普通 SELECT(无锁读)免于加S锁,但一旦显式加锁(如 SELECT ... LOCK IN SHARE MODE)或隔离级别设为 SERIALIZABLE,InnoDB就会真实申请S锁。此时MVCC快照机制完全失效,所有并发读都会落地为真实锁竞争。
关键点在于:
-
REPEATABLE READ下普通读走MVCC,不加S锁;但加锁读仍会触发S锁 -
READ COMMITTED下S锁生命周期更短(语句级),比RR更不易饥饿,但牺牲一致性 - 即使开了MVCC,
FOR UPDATE和LOCK IN SHARE MODE的锁行为始终遵循S/X兼容矩阵,不受MVCC影响
为什么SHOW ENGINE INNODB STATUS看不到饥饿线程?
MySQL的锁监控(如 SHOW ENGINE INNODB STATUS)只显示当前持锁和**正在等待锁**的事务,但不会标记“已排队但尚未进入等待队列”的写线程。很多X锁请求其实在进入InnoDB锁子系统前就被连接层或锁队列缓冲区拦住,表现为客户端超时或连接堆积,而非锁等待列表里的活跃条目。
排查建议:
- 用
performance_schema.data_locks查看实时S锁持有者(注意:需开启对应instrumentation) - 检查
information_schema.INNODB_TRX中TRX_STATE = 'LOCK WAIT'的事务,但更要关注TRX_STARTED时间远早于当前时间却仍未获得X锁的事务 - 监控
Threads_running和Aborted_clients突增,往往是写饥饿引发连接超时的间接信号
避免饥饿不是靠“加索引”就能解决的
索引优化能缩小X锁范围(避免表锁退化),但对S锁饥饿无直接作用。S锁饥饿本质是锁调度策略问题,而非锁粒度问题。InnoDB没有“写优先”或公平队列机制,S锁请求永远优先于X锁排队——哪怕X锁请求先到。
真正有效的缓解手段包括:
- 业务层控制读写比例:把高频只读逻辑迁移到从库,主库专注写入
- 缩短事务时间:S锁必须在事务内释放,长事务=长S锁持有
- 慎用显式共享锁:除非强一致性要求,否则优先用MVCC无锁读
- 必要时降级隔离级别:从
REPEATABLE READ改为READ COMMITTED,让S锁在语句结束后释放
最隐蔽的风险是:你以为自己没加锁,但ORM框架(如Hibernate)可能在二级缓存刷新或脏读检测时悄悄发出了 LOCK IN SHARE MODE ——这种隐式锁最难定位,也最容易引发饥饿。


















