共享锁(s锁)不能保证一致性读,真正提供一致性读的是mvcc+可重复读隔离级别下事务启动时生成的read view;lock in share mode仅防止并发修改,适用于先读再判再操作的业务场景。

共享锁(S锁)不能单独保证“读取一致性”,它只保证“不被写覆盖”;真正需要一致性读,得靠 MVCC + 可重复读隔离级别,而不是靠 LOCK IN SHARE MODE。
为什么 SELECT ... LOCK IN SHARE MODE 不是“一致性读”的解法
很多人误以为加了共享锁就能让多个事务看到相同数据快照,其实不是。共享锁只阻止其他事务对同一行加排他锁(X),但不阻止它们提交后的新版本被后续查询看到——尤其在 READ COMMITTED 隔离级别下,每次 SELECT 都会看到最新已提交数据。
- 共享锁本身不触发 MVCC 版本快照创建,它只是在当前时刻“占住一行不让改”
- 如果事务 A 执行
SELECT ... LOCK IN SHARE MODE后,事务 B 提交了UPDATE,事务 A 再执行一次普通SELECT就能看到新值(除非它也在可重复读下开启事务快照) - 真正提供一致性读的是事务启动时生成的 Read View,和是否加锁无关
什么场景下才该用 LOCK IN SHARE MODE
它的核心价值是“防止并发修改导致逻辑冲突”,比如库存校验、余额预占、选课抢号等需要“先读再判再操作”的业务链路。
- 典型模式:
BEGIN→SELECT stock FROM item WHERE id = 123 LOCK IN SHARE MODE→ 判断stock > 0→UPDATE item SET stock = stock - 1 WHERE id = 123→COMMIT - 如果不加锁,两个事务同时读到
stock = 1,都判断通过,结果扣成-1 - 加了 S 锁后,第二个事务的
UPDATE会被阻塞,直到第一个事务提交释放锁,从而串行化校验过程 - 注意:必须搭配
BEGIN显式开启事务,否则锁在语句结束后立即释放
容易踩的坑:锁范围、索引与死锁风险
共享锁生效的前提是走索引;没索引或走全表扫描时,InnoDB 会升级为表级锁,极大降低并发度。
- 如果
WHERE条件未命中索引(比如对非索引字段查询),LOCK IN SHARE MODE实际会对整张表加 S 锁 - 二级索引查询会锁二级索引记录 + 对应主键记录(即聚簇索引),不是只锁一个字段
- 若事务 A 先锁 id=1,再锁 name='a';事务 B 反过来操作,就可能形成死锁——MySQL 虽能检测并回滚一方,但应用层仍需避免这种交叉顺序
-
SELECT ... LOCK IN SHARE MODE在唯一索引 + 等值查询下只锁匹配行;范围查询(如WHERE id > 100)会锁住间隙(gap lock),影响插入
替代方案:多数情况下,优先考虑无锁 + 原子更新
只要业务允许,比加 S 锁更轻量、更安全的做法是绕过“读-判-写”三步,直接用一条原子 SQL 完成校验和变更。
- 例如库存扣减:
UPDATE item SET stock = stock - 1 WHERE id = 123 AND stock > 0 - 执行后检查
ROW_COUNT()是否为 1,为 0 表示库存不足,无需锁、无死锁、无事务等待 - 这种写法依赖 MySQL 的行锁自动机制(UPDATE 自动加 X 锁),且天然具备一致性:条件判断和更新在同一行锁保护下完成
- 仅当需要在读之后做复杂业务逻辑(比如跨表校验、调外部服务)时,才不得不引入
LOCK IN SHARE MODE
真正难的不是怎么加锁,而是判断“这里到底需不需要锁”——很多所谓“并发问题”,其实用带条件的原子更新就能消灭,反而加锁增加了死锁和长事务风险。











