库存扣减:高并发下的库存扣减方案

场景:50万人抢购100台iPhone 📱

想象这个场景:

秒杀开始

  • 库存:100台iPhone 15 Pro
  • 参与用户:50万人
  • 同时点击:10万人

问题

  • 如何确保100台iPhone正好卖给100个用户?
  • 如何避免超卖(卖出101台)?
  • 如何避免少卖(只卖出99台)?

这是一个经典的并发扣减库存问题!

问题演示:并发扣减库存的坑

方案一:无锁方案(最简单但会超卖)

典型输出

🧪 测试无锁方案:

  用户1: 读到库存=100
  用户2: 读到库存=100
  用户3: 读到库存=100
  ...
  用户1: 购买成功!库存=99
  用户2: 购买成功!库存=99  ← 两人同时买到!
  用户3: 购买成功!库存=98

📊 结果:
   初始库存: 100
   剩余库存: 90
   已售数量: 10
   总交易: 20
   
   ❌ 超卖!实际售出10件,但库存只减少了10件
   ❌ 数据不一致!

问题分析

时间轴:
T1: 用户A读库存=100
T2: 用户B读库存=100  ← A和B读到相同的值!
T3: 用户A判断100>0,准备扣减
T4: 用户B判断100>0,准备扣减
T5: 用户A扣减库存=99
T6: 用户B扣减库存=99  ← 应该是98!
T7: 用户A创建订单
T8: 用户B创建订单  ← 两个订单,但只扣了一次库存

❌ 超卖!

方案二:数据库悲观锁(可靠但性能差)

方案三:数据库乐观锁(有一定改进)

方案四:Redis原子操作(推荐方案)⚡

方案对比总结

Redis原子操作的实现细节

Lua脚本详解

Python实现

Redis vs 数据库:架构演进

想一想

问题 1:为什么Redis性能这么高?

提示

  • 内存操作 vs 磁盘操作
  • 单线程无锁竞争
  • 原子操作无需加锁

问题 2:Redis扣减库存后,MySQL库存如何同步?

提示

  • 异步MQ消息
  • 定时对账
  • 最终一致性

问题 3:Redis故障怎么办?

提示

  • Redis持久化(AOF/RDB)
  • Redis集群高可用
  • 故障降级方案