SELECT ... FOR UPDATE适用于写操作频繁、冲突概率高且失败重试代价大的场景,如库存扣减,需在InnoDB事务中命中索引执行;version乐观锁适用于读多写少、冲突低的场景,如浏览数更新,要求应用层处理校验失败;LOCK IN SHARE MODE适用于需防写不防读的中间态场景,如审核时查看订单。

什么时候该用 SELECT ... FOR UPDATE
当业务逻辑中「写操作频繁、冲突概率高」,且「失败重试代价大」时,SELECT ... FOR UPDATE 是更稳妥的选择。比如库存扣减:用户下单前必须确认库存充足,一旦查到有货,就要立刻锁住这行,防止其他并发请求同时扣减导致超卖。
关键前提是:语句必须在事务内执行(BEGIN / COMMIT 包裹),且数据库引擎为 InnoDB;否则会退化为表锁甚至报错。
-
FOR UPDATE只对命中索引的行加行锁;若WHERE条件没走索引,InnoDB 会升级为表锁,严重拖慢整体性能 - 锁持有时间 = 从执行
FOR UPDATE到事务COMMIT或ROLLBACK的整个区间;长时间事务会放大阻塞风险 - 多个事务交叉加锁顺序不一致(如 A 先锁 id=1 再锁 id=2,B 反过来),极易触发死锁;需统一加锁顺序或捕获
Deadlock found when trying to get lock错误后重试
什么时候该用 version 字段做乐观锁
当读多写少、冲突概率低,且业务能接受「冲突时失败或重试」时,version 字段是最轻量的乐观锁实现。典型场景是商品详情页的浏览数更新:UPDATE product SET browse_count = browse_count + 1, version = version + 1 WHERE id = ? AND version = ?。
它不依赖数据库锁机制,全程无阻塞,吞吐量高;但要求每次更新都带上旧 version 值,且应用层必须处理 0 rows affected 的情况(说明已被别人抢先更新)。
- 必须确保
version字段初始值为 0 或明确可控,避免因默认 NULL 导致条件恒假 -
WHERE中的version = ?必须与查询时拿到的值严格一致;不能用SELECT ... FOR UPDATE查完再拼 SQL,否则失去乐观意义 - 不适合多步更新场景(如先查库存、再查价格、再扣减);因为每一步都可能被并发修改,校验点分散会导致逻辑复杂甚至漏校验
LOCK IN SHARE MODE 和共享锁的适用边界
LOCK IN SHARE MODE 是悲观锁的一种,但它只阻止写,不限制其他事务读——适合「需要保证读一致性,又不希望完全阻塞读」的中间态场景。例如审核流程中,管理员查看订单明细时加共享锁,防止他人在此期间修改订单状态,但允许客服继续查单。
注意它和 FOR UPDATE 的核心差异:前者允许多个事务同时加 S 锁,后者只允许一个 X 锁;S 锁与 X 锁互斥,X 锁与 X 锁也互斥。
- 如果业务只需要「防写不防读」,用
LOCK IN SHARE MODE比FOR UPDATE更宽松,降低锁争用 - 但它不能替代
FOR UPDATE用于写前校验——因为其他事务仍可读到旧值并基于此做判断,造成逻辑错误 - 在
READ COMMITTED隔离级别下,S 锁只在当前语句生命周期内有效;升级到REPEATABLE READ才会持续到事务结束
容易被忽略的底层约束
无论选哪种锁,真正起作用的不是语法本身,而是 InnoDB 的事务机制和索引结构。没有主键或唯一索引的表,FOR UPDATE 会锁全表;没有 BEGIN 的单条语句,即使写了 FOR UPDATE 也会自动提交并立即释放锁——等于白加。
乐观锁看似简单,但版本号校验失败后的重试逻辑,如果没做幂等控制或最大重试限制,可能引发无限循环或雪崩式查询压力。


















