You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django的select_for_update能否用于获取读锁解决库存竞态问题?

问题解答

1. 该方案是否能达到预期效果?

可以达到防超卖的预期效果,但存在冗余设计。
select_for_update本身会对查询命中的Product表行加数据库级别的排他锁,同个商品的多个并发请求执行到Product.objects.select_for_update.get(...)这一行时,只有第一个拿到锁的事务可以继续执行,其余请求会全程阻塞直到前一个事务提交或回滚,不需要额外通过新增version字段、修改字段值的方式来触发锁。
你原本的逻辑中,拿到锁后计算可用库存、创建购买记录的操作都是在持有排他锁的事务内执行的,不会出现多个事务同时读取到相同可用库存、重复扣减的竞态问题。

额外提示:你当前的amount_available_for_purchase属性存在隐藏bug:当该商品还没有任何购买记录时,聚合查询返回的sum__amount值为None,会导致减法运算报错,需要补充默认值处理。

2. 该方案是否足够简洁?如何不大幅重构实现需求?

当前方案不够简洁,冗余的version字段和对应的更新操作完全可以去掉,无需修改核心业务逻辑,优化成本极低:

第一步:恢复Product模型,删除冗余字段

class Product(models.Model):
   amount_in_storage = models.PositiveIntegerField() # 初始化后固定不可修改

   @property
   def amount_available_for_purchase(self):
       # 补充空值处理
       sold_total = Purchase.objects.filter(product=self.id).aggregate(models.Sum('amount'))["sum__amount"]
       return self.amount_in_storage - (sold_total if sold_total is not None else 0)

第二步:简化购买逻辑,去掉无用的版本更新操作

@atomic_transaction
def func(product_id, amount_to_purchase):
   # 该行直接获取商品行排他锁,其余同商品的并发请求会阻塞在此处
   product = Product.objects.select_for_update().get(id=product_id)

   if product.amount_available_for_purchase > amount_to_purchase:
     # 执行支付相关逻辑
     Purchase.objects.create(product=product, amount=amount_to_purchase)
     # 执行其他后续逻辑
   else:
     # 补充库存不足的异常处理即可
     raise ValueError("商品可用库存不足")

优化后的方案完全保留了你原本的业务逻辑,只做了减法,同时减少了一次不必要的Product表更新操作,性能优于你原本的方案,常规电商量级的并发完全可以稳定支撑。

内容的提问来源于stack exchange,提问作者A. Khaled

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.04 15:21:02