乐观锁:使用版本号实现乐观锁

问题:悲观锁太慢,如何提升性能?🤔

上一节我们学习了悲观锁,它有一个致命问题:

悲观锁性能测试结果:
- 1000 QPS:响应时间 100ms
- 5000 QPS:响应时间 500ms  
- 10000 QPS:响应时间 1000ms+
- 秒杀场景需要 500000 QPS ❌ 完全不够用!

问题根源

所有请求排队等待锁 🔒
线程A ━━━━━━━━━━━━━━┫
线程B          ⏳ ━━━━━━━━━━━━━━┫
线程C                    ⏳ ━━━━━━━━━━━━━━┫
线程D                              ⏳ ━━━━━━━━━━━━━━┫

每个请求必须等前一个请求完全完成才能开始

新的思路

  • 悲观锁:假设一定会冲突,先加锁再操作
  • 乐观锁:假设大概率不冲突,先操作,冲突时重试

乐观锁的核心思想 💡

生活中的乐观锁例子

数据库乐观锁实现

方案1:版本号机制

数据设计要点

  • 这是一次表结构演进:随着业务能力增加,把新状态、新时间点或新归属关系补进数据模型。
  • 查询目标是快速定位状态、任务或资源,避免在关键路径上做大范围扫描。

完整方案落地

方案2:CAS(Compare And Swap)

并发测试:对比乐观锁和悲观锁

测试结果对比

============================================================
开始对比测试:100个用户抢购
============================================================

🔒 测试悲观锁...
悲观锁结果:
  成功:10/100
  耗时:1234.56ms
  平均响应:12.35ms

🔓 测试乐观锁...
乐观锁结果:
  成功:10/100
  耗时:456.78ms
  平均响应:4.57ms

============================================================
性能对比:
============================================================
悲观锁:耗时 1234ms
乐观锁:耗时 456ms
性能提升:2.7倍 🚀

乐观锁的性能分析

为什么乐观锁更快?

乐观锁的优缺点

优点 ✅

优点说明适用场景
并发性能高读取不加锁,并发度高读多写少
无死锁不需要获取锁,不会死锁复杂事务
实现简单只需添加版本号字段快速开发
数据库压力小不需要维护锁高并发

缺点 ❌

缺点说明影响
ABA问题值从A改成B又改成A,无法感知部分场景
冲突重试高冲突时大量重试,性能下降秒杀场景严重
用户体验冲突时需要重试,可能失败用户感知
业务侵入需要处理重试逻辑代码复杂度

ABA问题详解

乐观锁的最佳实践

性能对比总结

不同场景的性能对比

场景并发量库存悲观锁性能乐观锁性能推荐
后台管理10充足⭐⭐⭐⭐⭐⭐⭐⭐乐观锁
普通电商100充足⭐⭐⭐⭐⭐⭐⭐⭐⭐乐观锁
促销活动1000100⭐⭐⭐⭐⭐⭐⭐乐观锁
秒杀活动10000100⭐⭐⭐⭐都不够
超级秒杀100000100都不够

关键结论

想一想

问题 1:乐观锁为什么在秒杀场景性能下降?

提示

  • 秒杀:50万人抢100台手机
  • 冲突率 = 并发数 / 库存 = 500000 / 100 = 5000倍
  • 意味着什么?

问题 2:如何优化乐观锁在秒杀场景的表现?

提示

  • 能否减少并发数?(限流)
  • 能否减少冲突?(队列)
  • 能否提前筛选?(缓存)

问题 3:还有什么更好的方案?

提示

  • 数据库是瓶颈
  • 能否不用数据库?
  • Redis、消息队列?