select ... for share会加s锁,与x锁互斥且可能引发死锁。它在非唯一索引下触发next-key lock,锁住区间而非单行;多事务加锁顺序不一致或后续dml升级锁类型时易形成循环等待。

共享锁之间不互斥,但S锁和X锁互斥
很多人误以为SELECT ... FOR SHARE或SELECT ... LOCK IN SHARE MODE只是“读锁”,不会引发死锁。错在忽略了它和FOR UPDATE、UPDATE、DELETE等操作之间的锁冲突本质:S锁和X锁天然互斥。
常见错误现象:
- 事务A执行
SELECT * FROM order WHERE user_id = 123 FOR SHARE,拿到S锁 - 事务B紧接着执行
UPDATE order SET status = 'paid' WHERE user_id = 123,试图加X锁 → 被阻塞 - 若此时事务B已持有另一行(如
user_id = 125)的X锁,而事务A又恰好要对该行加S锁 → 循环等待成立,MySQL立即检测并回滚其中一个事务
非唯一索引触发Next-Key Lock,锁范围远超预期
当FOR SHARE语句的WHERE条件命中非唯一索引(比如user_id普通索引),InnoDB会加Next-Key Lock(记录锁 + 间隙锁),锁住的是一个区间,不是单行。
实操建议:
- 用
EXPLAIN确认查询是否走了索引;没走索引 → 全表扫描 → 所有行加S锁 → 死锁概率飙升 - 若字段无法建唯一索引,至少加普通索引,并配合
ORDER BY id显式控制扫描顺序,避免因优化器选择不同执行计划导致锁序不一致 - 检查
information_schema.INNODB_TRX和INNODB_LOCK_WAITS,看死锁日志里锁的type是否为RECORD或GAP,确认是否涉及间隙
多个事务交叉加锁且顺序不一致
死锁核心是循环等待。只要两个事务以相反顺序请求同一组资源,哪怕全是S锁,也可能因后续升级为X锁而触发死锁链。
典型场景:
- 事务A:
SELECT ... WHERE id IN (1, 2) FOR SHARE→ 拿到id=1、2的S锁 → 再执行UPDATE ... WHERE id = 2(尝试升级为X锁) - 事务B:
SELECT ... WHERE id IN (2, 1) FOR SHARE→ 拿到id=2、1的S锁 → 再执行UPDATE ... WHERE id = 1 - 事务A卡在id=2的X锁升级,事务B卡在id=1的X锁升级 → 形成A→B→A闭环
关键点:FOR SHARE本身不升级,但后续DML会触发锁类型转换;而不同事务对同一组ID的遍历顺序不一致,就是埋雷。
为什么FOR SHARE比快照读更危险?
SELECT ... FOR SHARE不是MVCC快照读,它真实参与锁竞争,阻塞所有后续X锁请求——包括其他事务的FOR UPDATE、UPDATE、INSERT(如果涉及相同索引间隙)。
容易被忽略的地方:
- 缓存层随机取一批ID(如
[101, 205, 98])后直接遍历FOR SHARE,不同请求间ID顺序不固定 → 锁序天然混乱 - 业务逻辑中混用
FOR SHARE查A行 +FOR UPDATE改B行,却没有统一按主键升序加锁 → 看似无关的操作,实际构成资源依赖环 - 隔离级别为
REPEATABLE READ时,Next-Key Lock自动启用,哪怕你只想要行级S锁,间隙也被锁住,别人插不进数据,也改不了相邻行
真正难防的不是锁本身,而是S锁带来的“假安全感”——它不改数据,却悄悄堵住了所有写入通路。











