SELECT ... LOCK IN SHARE MODE 是显式加 S 锁的锁申请行为,会在匹配行及间隙加锁,导致其他事务对相同行的写操作阻塞,仅在显式事务中生效,性能开销源于锁管理而非查询本身。

SELECT ... LOCK IN SHARE MODE 不是“简单查询”,它是显式加读锁操作
很多人以为 SELECT ... LOCK IN SHARE MODE 只是带个修饰的普通查询,其实它本质是事务内的一次**锁申请行为**,和 SELECT 单独执行有根本区别。InnoDB 会在匹配行上加 **S 锁(共享锁)**,同时在索引间隙也可能加 Gap Lock —— 这正是它常被误认为“莫名卡住”的原因。
常见错误现象:
– 同一事务中后续 UPDATE 被阻塞
– 其他事务对相同行执行 SELECT ... FOR UPDATE 时等待超时
– SHOW ENGINE INNODB STATUS 显示 LOCK_MODE: S 或 S,G
- 它只在事务中生效(自动开启隐式事务不算,必须显式
BEGIN或已处于活跃事务) - 不加
WHERE条件会锁全表(或全聚簇索引),等效于表级读锁 - 若
WHERE条件未命中索引,会退化为全表扫描 + 行锁堆积,甚至触发锁升级
为什么没写 UPDATE 却被其他写操作阻塞?
因为 LOCK IN SHARE MODE 的读锁(S 锁)和写锁(X 锁)互斥:只要某行被加上 S 锁,其他事务对该行的 UPDATE、DELETE 或 SELECT ... FOR UPDATE 就必须等待。这不是 bug,是 MVCC + 2PL 协同下的正常行为。
典型场景:
– A 事务执行 SELECT id, balance FROM accounts WHERE id = 100 LOCK IN SHARE MODE
– B 事务紧接着执行 UPDATE accounts SET balance = balance + 100 WHERE id = 100 → 阻塞
- 即使 A 事务后续不修改数据,S 锁也会持续到事务结束(
COMMIT或ROLLBACK) - RR 隔离级别下,还会额外持有 Gap Lock,可能阻塞相邻插入(比如 id=99 或 101 的新记录)
- 如果 A 事务迟迟不提交,B 事务就会卡在
updating or deleting状态,查information_schema.INNODB_TRX能看到trx_state = 'LOCK WAIT'
LockInShareMode 和普通 SELECT 的性能差异在哪?
差别不在 SQL 解析或执行计划上,而在**锁管理开销与并发压制效果**。普通 SELECT 在 RR 隔离级别下走快照读(MVCC),不加任何锁;而 LOCK IN SHARE MODE 强制当前读,并触发行锁/间隙锁的获取、校验、排队、释放全流程。
- 锁结构体分配、哈希查找、等待队列插入——这些都有 CPU 和内存开销
- 高并发下容易出现锁争用,
innodb_row_lock_waits计数器会上升 - 若涉及唯一索引等值查询,只锁匹配行;但范围查询(如
WHERE id > 100)会锁住整个区间,包括不存在的间隙 - 对比
SELECT ... FOR UPDATE:后者加 X 锁,更重,但语义更明确(“我要改”);前者加 S 锁,语义模糊(“我先看看,但别人别动”),反而更容易被滥用
什么时候真该用 LOCK IN SHARE MODE?
它不是为了“防止脏读”而存在(RR 级别下普通 SELECT 已能保证),而是用于**协调多个事务对同一数据的协作读取+后续写入**。真实适用场景极少,且必须满足两个前提:业务逻辑确需“读-改-写”原子性,且无法用乐观锁替代。
- 典型合理用法:库存扣减前校验余量,且校验与扣减必须在同一事务中串行执行(避免 A 查 100 → B 查 100 → A 扣 1 → B 扣 1 → 库存变 98 而非预期 99)
- 错误用法:日志类查询、报表导出、前端列表页加载 —— 这些场景用普通 SELECT + 应用层缓存更高效
- 替代方案优先级:乐观锁(version 字段) >
SELECT ... FOR UPDATE(如果你紧接着就要改) >LOCK IN SHARE MODE - 最容易被忽略的一点:它不会阻止其他事务对**未锁定行**的修改,所以“防并发”效果是局部的、脆弱的,依赖精确的 WHERE 条件和索引覆盖


















