乐观锁 vs 悲观锁

  1. 核心定义
    乐观锁 悲观锁

假设 冲突很少发生 冲突经常发生
检测时机 写入时检测 读取时就加锁
实现方式 version / updated_at / status + rowcount==0 SELECT … FOR UPDATE / UPDATE … WHERE
冲突代价 重试 排队等待
本质 延迟检测,用重试换吞吐 提前预防,用排队换确定

  1. 乐观锁三种载体(项目落点)
    载体 写法 适用

version UPDATE … SET version=version+1 WHERE id=? AND version=? 通用,最标准
updated_at UPDATE … WHERE id=? AND updated_at=? 时间戳精度足够时
status UPDATE … WHERE id=? AND status=’pending’ 状态机流转

冲突判定:rowcount == 0 → 并发冲突 → 重试或报错。

  1. 悲观锁写法
    – 读取时加排他锁,事务结束才释放
    SELECT * FROM inventory WHERE sku=’ABC’ FOR UPDATE;

– 或单条 UPDATE 天然加锁(锁持有极短)
UPDATE inventory SET qty=qty-1 WHERE sku=’ABC’ AND qty>=1;

  1. 适用场景
    选乐观锁 选悲观锁
    冲突概率低 冲突概率高
    读后写,事务较长 单行原子操作,事务极短
    跨多表/多服务编排 锁范围明确,就是某一行
    重试成本可接受 重试代价大(如支付回调)

  2. 面试问答模板
    Q:为什么用乐观锁不用悲观锁?

  • 我们场景是读后写,读取和写入之间隔了 Service 层业务编排。用悲观锁的话,锁持有时间会覆盖整个业务逻辑,导致锁竞争严重、吞吐下降。而且冲突概率在正常流量下不高,乐观锁重试成本可接受。乐观锁把锁粒度从“整个事务”缩小到“UPDATE 那一瞬间”。

Q:什么时候该用悲观锁?

  • 冲突概率高、事务极短、单行原子操作的场景。比如库存扣减的 UPDATE inventory SET qty=qty-1 WHERE sku=? AND qty>=1,天然悲观锁,锁持有毫秒级。

Q:两者冲突吗?

  • 不冲突,可共存。同一系统不同模块各用各的;同一操作可先乐观后悲观降级;架构层面可 Redis 预扣减 + 数据库乐观锁 + 极端场景悲观锁兜底。