Django如何处理库存为1时两名用户同时购买实物商品的问题
Django电商库存并发校验与购物车自动清理实现方案
一、核心设计思路
两个需求对应两个校验节点即可实现,核心是通过数据库行锁解决并发购买的竞态问题,避免超卖:
- 提交订单/进入支付前做实时库存校验,拦截库存不足的结算请求
- 购物车页面加载时先做全量库存校验,自动清理无库存商品并提示用户
二、具体实现步骤
1. 模型层基础准备
先定义商品、购物车核心模型字段,示例如下:
from django.db import models from django.contrib.auth.models import User # 商品模型 class Product(models.Model): name = models.CharField(max_length=100) stock = models.IntegerField(default=0) # 可售库存 # 其余商品字段按需补充 # 购物车条目模型 class CartItem(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE) product = models.ForeignKey(Product, on_delete=models.CASCADE) quantity = models.IntegerField(default=1) add_time = models.DateTimeField(auto_now_add=True)
2. 进入支付环节的并发校验逻辑(解决第一个需求)
必须用select_for_update对商品行加排他锁,避免两个并发请求同时查询到剩余库存1都通过校验的问题,所有校验和库存扣减放在同一个事务中保证原子性,示例代码如下:
from django.db import transaction from django.contrib import messages @transaction.atomic def create_order(request): # 获取用户本次选择结算的购物车条目ID列表 settle_cart_ids = request.POST.getlist('cart_item_ids') cart_items = CartItem.objects.filter(user=request.user, id__in=settle_cart_ids) # 提取关联商品ID,加行锁锁定商品行,直到当前事务结束其他请求才能修改该商品数据 product_ids = [item.product_id for item in cart_items] locked_products = Product.objects.select_for_update().filter(id__in=product_ids) product_map = {p.id: p for p in locked_products} for item in cart_items: current_product = product_map[item.product_id] # 任意商品库存不足直接终止结算,返回错误 if current_product.stock < item.quantity: messages.error(request, f"商品【{current_product.name}】库存不足,无法进入支付流程") return redirect('cart_list_page') # 校验通过扣减库存 current_product.stock -= item.quantity current_product.save(update_fields=['stock']) # 后续创建待支付订单、跳转支付页逻辑按需补充 return redirect('pay_page')
3. 购物车加载时自动清理逻辑(解决第二个需求)
用户登录后访问购物车页面时,先全量校验所有购物车商品库存,删除无库存商品后再返回页面,示例代码如下:
def cart_list_page(request): if not request.user.is_authenticated: return redirect('login_page') cart_items = CartItem.objects.filter(user=request.user).select_related('product') removed_product_names = [] for item in cart_items: if item.product.stock < item.quantity: removed_product_names.append(item.product.name) item.delete() # 有被删除的商品则给用户提示 if removed_product_names: messages.warning(request, f"以下商品因库存不足已从购物车移除:{','.join(removed_product_names)}") # 查询剩余有效购物车条目返回页面 valid_cart_items = CartItem.objects.filter(user=request.user).select_related('product') return render(request, 'cart/list.html', {'cart_items': valid_cart_items})
三、优化补充点
- 若业务存在秒杀等高并发场景,可将库存校验逻辑迁移至Redis实现,性能更高,普通电商场景上述数据库行锁方案完全可以满足需求
- 建议给待支付订单增加超时自动取消逻辑,例如15分钟未支付自动回滚库存,避免用户占库存不付款导致其他用户无法购买
- 库存扣减与订单创建必须放在同一个数据库事务中执行,避免出现库存扣减成功但订单创建失败的不一致问题
内容的提问来源于stack exchange,提问作者Gabriel Paschall
相关产品推荐
相关产品推荐

