lock in share mode 只在事务中生效,需配合 select 使用且依赖索引命中、合适隔离级别及后续更新逻辑;否则看似加锁实则无效。

LOCK IN SHARE MODE 什么时候真正起作用
它只在事务里生效,且必须配合 SELECT 使用——单独执行 SELECT ... LOCK IN SHARE MODE 不会锁住数据,除非你已经用 BEGIN 或 START TRANSACTION 开启了事务。
常见错误现象:SELECT * FROM users WHERE id = 100 LOCK IN SHARE MODE; 执行完立刻释放锁(因为没在事务中),别人紧接着就能 UPDATE 同一行。
- 必须先
BEGIN,再执行带LOCK IN SHARE MODE的SELECT - 锁持续到事务结束(
COMMIT或ROLLBACK) - 如果事务里还有后续操作(比如校验后决定是否插入),共享锁能防止并发修改破坏一致性
和 SELECT FOR UPDATE 的关键区别在哪
LOCK IN SHARE MODE 允许其他事务也加共享锁,但禁止加排他锁;SELECT FOR UPDATE 直接加排他锁,别人连共享锁都加不了。
使用场景差异明显:比如「查余额 → 判定是否足够 → 扣款」流程中,若只是读取校验不打算改,用 LOCK IN SHARE MODE 可让多个查询并发读同一行;但一旦涉及更新,就得用 FOR UPDATE,否则可能被别的事务抢先改掉。
- 两个事务同时执行
SELECT ... LOCK IN SHARE MODE→ 都成功 - 一个事务用了
LOCK IN SHARE MODE,另一个尝试SELECT ... FOR UPDATE→ 阻塞,直到第一个事务结束 -
UPDATE/DELETE语句本身隐式加排他锁,不需要额外写FOR UPDATE
为什么有时候 LOCK IN SHARE MODE 像没锁一样
最常踩的坑是隔离级别太低,或者没走索引。
MySQL 的行锁依赖索引。如果 WHERE 条件没命中索引(比如对非索引字段查询、或用了函数如 UPPER(name)),InnoDB 会退化为表级锁或锁住所有扫描过的记录,甚至锁失效——看起来“没锁住”,其实是锁了不该锁的范围,或者根本没锁到目标行。
- 务必确认执行计划里
type是const/ref,key显示用了哪个索引 - 在
READ COMMITTED隔离级别下,LOCK IN SHARE MODE只锁当前读到的行,不锁 gap;但在REPEATABLE READ下会锁 gap,防止幻读 - 如果表没主键或唯一索引,InnoDB 会用隐藏的
row_id,但此时加锁行为更难预测,尽量避免
并发更新时容易漏掉的校验环节
加了 LOCK IN SHARE MODE 只是保证“读的时候值没变”,不代表“提交时值仍可用”。比如两个事务都读到 stock = 5,都判断够用,然后都去 UPDATE,结果变成 stock = 3 而不是预期的 0。
这不是锁的问题,而是业务逻辑缺原子性校验。真正的防护得靠 UPDATE ... SET stock = stock - 1 WHERE id = 100 AND stock >= 1 这类带条件的更新,再检查 ROW_COUNT() 是否为 1。
-
LOCK IN SHARE MODE解决的是“读-改”之间的中间态被篡改,不是替代乐观/悲观校验 - 高并发扣减类场景,光靠共享锁不够,必须结合 WHERE 条件 + 影响行数判断
- 如果业务允许超卖,那锁反而成了性能瓶颈;不允许超卖,就得在更新语句里把约束带上
共享锁真正起效的前提很具体:事务开启、索引命中、隔离级别匹配、后续有对应更新动作。少一个条件,就容易变成“以为锁住了,其实没锁住”。










