乐观锁和悲观锁需依竞争强度、业务语义、延迟要求动态权衡;悲观锁适用于写多读少、高冲突、强一致场景,如库存扣减;乐观锁通过版本号校验实现延迟冲突检测;混合策略与绕过锁设计可进一步提升性能与可靠性。

乐观锁和悲观锁不是非此即彼的选择,而是要根据数据竞争强度、业务语义、响应延迟要求来动态权衡。用错锁机制,轻则性能陡降,重则引发死锁或丢失更新。
什么时候该用悲观锁?
适合写多读少、事务周期短、冲突概率高、且必须强一致的场景。比如库存扣减、账户余额转账、秒杀下单。
- 用 SELECT ... FOR UPDATE 显式加行级写锁,但务必确保 WHERE 条件命中索引,否则会升级为表锁
- 事务粒度要尽量小——加锁后立刻执行更新,避免在锁持有期间做 HTTP 调用、日志打印等耗时操作
- 设置合理的 innodb_lock_wait_timeout(默认 50 秒),配合应用层超时与重试逻辑,防止线程长时间阻塞
- 注意死锁风险:多个事务按不同顺序访问同一组行时极易触发。可通过统一访问顺序(如按主键升序)或使用 SELECT ... FOR UPDATE SKIP LOCKED 规避
乐观锁不是“不加锁”,而是“延迟校验”
它依赖版本号(version)、时间戳(updated_at)或业务字段(如库存余量)做并发控制,在 UPDATE 时验证是否被修改过。失败不阻塞,由应用决定重试或提示。
- 推荐用 version 字段(BIGINT 或 INT)而非时间戳,避免毫秒级并发下时钟漂移或重复值问题
- UPDATE 语句必须包含版本条件,例如:
UPDATE order SET status = 'paid', version = version + 1 WHERE id = ? AND version = ? - 影响行数为 0 即表示更新失败,需捕获并处理(如重读+重算+重提交,或返回“操作已被他人修改”)
- 慎用于高频更新+长事务场景:反复重试可能放大数据库压力,此时应评估是否误用了乐观锁
混合策略:乐观校验 + 悲观兜底
对关键路径做轻量乐观校验,失败后再以悲观方式保底执行,兼顾性能与可靠性。
- 例如优惠券核销:先用 version 字段乐观更新;失败后,查当前库存是否充足,若仍可扣减,则用 SELECT ... FOR UPDATE 锁定该券记录再执行
- 适用于“绝大多数请求无冲突,但极少数需强保障”的混合负载,比全程悲观锁吞吐高,又比纯乐观锁容错强
- 注意兜底逻辑不能嵌套在原事务中盲目重试,建议拆分为独立子事务或补偿流程,避免锁等待叠加
绕过锁:从设计上减少竞争
最高效的锁优化,是让锁根本不需要出现。
- 分库分表 + 热点隔离:把用户订单分散到不同库表,避免所有更新集中在单条记录上
- 状态机驱动:用异步消息+状态流转替代同步强一致性更新(如“支付中→已支付”由 MQ 消费端完成)
- 缓存预占:Redis 中预扣库存(INCRBY)+ 过期时间,成功后再落库;失败则释放缓存,降低 DB 冲突面
- 批量合并:将多次小更新聚合成一次大更新(如积分累加改为 delta 累计,定时 flush),减少锁频次










