乐观锁 vs 悲观锁
- 核心定义
乐观锁 悲观锁
假设 冲突很少发生 冲突经常发生
检测时机 写入时检测 读取时就加锁
实现方式 version / updated_at / status + rowcount==0 SELECT … FOR UPDATE / UPDATE … WHERE
冲突代价 重试 排队等待
本质 延迟检测,用重试换吞吐 提前预防,用排队换确定
- 乐观锁三种载体(项目落点)
载体 写法 适用
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 → 并发冲突 → 重试或报错。
- 悲观锁写法
– 读取时加排他锁,事务结束才释放
SELECT * FROM inventory WHERE sku=’ABC’ FOR UPDATE;
– 或单条 UPDATE 天然加锁(锁持有极短)
UPDATE inventory SET qty=qty-1 WHERE sku=’ABC’ AND qty>=1;
适用场景
选乐观锁 选悲观锁
冲突概率低 冲突概率高
读后写,事务较长 单行原子操作,事务极短
跨多表/多服务编排 锁范围明确,就是某一行
重试成本可接受 重试代价大(如支付回调)面试问答模板
Q:为什么用乐观锁不用悲观锁?
- 我们场景是读后写,读取和写入之间隔了 Service 层业务编排。用悲观锁的话,锁持有时间会覆盖整个业务逻辑,导致锁竞争严重、吞吐下降。而且冲突概率在正常流量下不高,乐观锁重试成本可接受。乐观锁把锁粒度从“整个事务”缩小到“UPDATE 那一瞬间”。
Q:什么时候该用悲观锁?
- 冲突概率高、事务极短、单行原子操作的场景。比如库存扣减的 UPDATE inventory SET qty=qty-1 WHERE sku=? AND qty>=1,天然悲观锁,锁持有毫秒级。
Q:两者冲突吗?
- 不冲突,可共存。同一系统不同模块各用各的;同一操作可先乐观后悲观降级;架构层面可 Redis 预扣减 + 数据库乐观锁 + 极端场景悲观锁兜底。