悲观锁:使用数据库悲观锁保证一致性

问题:50万人抢100台手机,如何保证不超卖?😰

想象一下这个场景:

秒杀开始

  • 库存:100台iPhone
  • 参与用户:50万人
  • 并发请求:瞬间涌入50万次
  • 你的任务:保证不多卖一台,不少卖一台

传统方案的问题

超卖现象演示

时间线:
T1: 线程A读取库存 = 1
T2: 线程B读取库存 = 1  ← 都看到还有1台
T3: 线程A判断库存充足,下单
T4: 线程B判断库存充足,下单  ← 都认为可以买
T5: 线程A扣减库存:stock = 0
T6: 线程B扣减库存:stock = -1  ← 超卖了!

结果:库存1台,卖出2台,超卖!

解决方案:悲观锁 🔒

悲观锁的核心思想:假设冲突一定会发生,操作前先加锁

数据库悲观锁的实现

数据设计要点

  • 查询目标是快速定位状态、任务或资源,避免在关键路径上做大范围扫描。

完整方案落地

并发测试:验证悲观锁的正确性

测试输出

🚀 开始并发测试:20个用户同时抢购

============================================================
🔒 用户1尝试获取商品1的锁...
✅ 用户1获得锁,当前库存:10
🔒 用户2尝试获取商品1的锁...  ← 等待中...
🔒 用户3尝试获取商品1的锁...  ← 等待中...
...
🎉 用户1购买成功,剩余库存:9
✅ 用户2获得锁,当前库存:9
🎉 用户2购买成功,剩余库存:8
✅ 用户3获得锁,当前库存:8
...
❌ 用户11:库存不足,stock=0
❌ 用户12:库存不足,stock=0
...

============================================================
📊 测试结果:
  总请求数:20
  成功数:10
  失败数:10
  耗时:245.32ms
  平均响应时间:12.27ms

悲观锁的工作原理 🔍

让我们深入理解悲观锁的底层机制:

悲观锁的优缺点分析

优点 ✅

缺点 ❌

性能对比表

并发量悲观锁表现平均响应时间数据库负载用户体验
100✅ 优秀10ms20%⭐⭐⭐⭐⭐
500✅ 良好50ms40%⭐⭐⭐⭐
1000✅ 可接受100ms60%⭐⭐⭐
2000⚠️ 一般200ms80%⭐⭐
5000⚠️ 较慢500ms95%
10000+❌ 不可用1s+100%崩溃 💥

最佳实践建议 💡

想一想

问题 1:悲观锁能支撑多少QPS?

根据我们的测试和经验数据:

  • 单机MySQL:最多支撑 1000-5000 QPS
  • 主从架构:悲观锁只能走主库,无法分担压力
  • 秒杀场景:50万QPS远远超出能力范围

你的思考:如果秒杀QPS是50万,悲观锁够用吗?

问题 2:如何优化悲观锁性能?

提示方向:

  • 能否减少锁的粒度?(行锁 vs 表锁)
  • 能否缩短事务时间?
  • 能否用缓存分担压力?

问题 3:什么场景适合用悲观锁?

考虑因素:

  • 并发量级
  • 一致性要求
  • 开发成本
  • 系统复杂度