-
在电商促销或秒杀活动或普通购买商品中,一般会有这样的问题:一件商品的库存只有5件,同时A用户买了5个,B用户买了5个,都提交数据,照成库存不足的问题(超卖问题)
-
如何解决这个问题有两个方案,悲观锁和乐观锁
-
悲观锁:在查询商品储存的时候加锁 select_for_update() 在发生事务的commit或者是事务的rollback时,自动释放该锁,这样其他用户就可以接着查询该商品
@transaction.atomic
def post(self, request):
"""接受数据 修改购物车数据 添加订单 和订单商品 加悲观锁 解决并发问题"""
sku_ids = request.POST.get('sku_ids')
addr_id =request.POST.get('addr_id')
pay_method = request.POST.get('pay_method')
transit_price = request.POST.get('transit_price')
user = request.user
total_price = 0
total_count = 0
order_id = datetime.now().strftime('%Y%m%d%H%M%S') + str(user.id)
# 设置事务保存点
tip = transaction.savepoint()
try:
new_order =OrderInfo.objects.create(order_id=order_id,
user=user,
addr=address,
pay_method=pay_method,
total_count=total_count,
total_price=total_price,
transit_price=transit_price)
except Exception as e:
# 回滚到tip
transaction.savepoint_rollback(tip)
return JsonResponse({'res': 3, 'errmsg': '生成订单失败'})
cart_key = 'cart_%d' % user.id
conn = get_redis_connection('default')
skus = conn.hgetall(cart_key)
for sku_id in eval(sku_ids): # 循环获得商品的信息 每条数据生成一个订单商品数据
# 获得购物车中每个商品的数量
sku_count = conn.hget(cart_key, sku_id)
if not sku_count: # 判断购物车里有没有该商品
# 回滚到tip
transaction.savepoint_rollback(tip)
return JsonResponse({'res': 4, 'errmsg': '该商品不在购物车中'})
try: # 在商品表中 找该商品sel
sku = GoodsSKU.objects.select_for_update().get(id=sku_id, is_show=1) # 加锁查询
except GoodsSKU.DoesNotExist:
# 回滚到tip
transaction.savepoint_rollback(tip)
return JsonResponse({'res': 5, 'errmsg': '该商品已下架'})
price = sku.price
sku_count = int(sku_count)
if sku_count > int(sku.count): # 判断库存
# 回滚到tip
transaction.savepoint_rollback(tip)
return JsonResponse({'res': 6, 'errmsg': '库存不足'})
price = int(price)
sku_price = sku_count * price
total_price += sku_price
total_count += sku_count
# 添加订单商品表
try:
OrderGoods.objects.create(order=new_order,
sku=sku,
count=sku_count,
price=sku_price,
)
except Exception as e:
# 回滚到tip
transaction.savepoint_rollback(tip)
return JsonResponse({'res': 7, 'errmsg': '订单商品创建失败'})
# 减少商品库存 增加商品销售额
sku.count -= sku_count
sku.sale_count += sku_count
sku.save()
# 删除修改购物车数据
conn.hdel(cart_key, sku_id)
total_price += int(transit_price)
# 修改order 订单数量 总价 邮费
new_order.total_count = total_count
new_order.total_price = total_price
new_order.transit_price = transit_price
new_order.save()
#释放保存点
transaction.savepoint_commit(tip)
- 乐观锁:乐观锁不是真正的锁,在创建订单之前查询商品的库存,在创建订单详情表前,update更新查询数据,如果两次查询的库存量一样就创建详情表,并减去库存,否则,循环三次,如果都不一样,就发生rollback
def post(self, request):
'''订单创建'''
# 接收参数
addr_id = request.POST.get('addr_id')
pay_method = request.POST.get('pay_method')
sku_ids = request.POST.get('sku_ids')
# 业务处理:订单创建
order_id = datetime.now().strftime('%Y%m%d%H%M%S')+str(user.id)
# 运费
transit_price = 10
# 总数目和总金额
total_count = 0
total_price = 0
# 设置保存点
sid = transaction.savepoint()
try:
order = OrderInfo.objects.create(order_id=order_id,
user=user,
addr=addr,
pay_method=pay_method,
total_count=total_count,
total_price=total_price,
transit_price=transit_price)
conn = get_redis_connection('default')
cart_key = 'cart_%d'%user.id
# todo: 用户的订单中包含几个商品,就应该向df_order_goods中添加几条记录
sku_ids = sku_ids.split(',') # [1,4]
for sku_id in sku_ids:
for i in range(3):
# 根据sku_id获取商品的信息
try:
# select * from df_goods_sku where id=sku_id;
sku = GoodsSKU.objects.get(id=sku_id)
except GoodsSKU.DoesNotExist:
transaction.savepoint_rollback(sid)
return JsonResponse({'res':4, 'errmsg':'商品信息错误'})
# 获取用户要购买的商品的数目
count = conn.hget(cart_key, sku_id)
# todo: 判断商品库存
if int(count) > sku.stock:
transaction.savepoint_rollback(sid)
return JsonResponse({'res':6, 'errmsg':'商品库存不足'})
# todo: 减少商品的库存,增加销量
origin_stock = sku.stock # 创建订单之前查询商品的库存
new_stock = origin_stock - int(count)
new_sales = sku.sales + int(count)
res = GoodsSKU.objects.filter(id=sku_id, stock=origin_stock).update(stock=new_stock, sales=new_sales) # 更新前查询看是否与之前的查询相等
if res == 0:
# 更新失败
if i == 2:
# 已经尝试了3次,下单失败
transaction.savepoint_rollback(sid)
return JsonResponse({'res':7, 'errmsg':'下单失败2'})
continue
# todo: 向df_order_goods中添加一条记录
OrderGoods.objects.create(order=order,
sku=sku,
count=count,
price=sku.price)
# todo: 累加计算商品的总件数和总金额
total_count += int(count)
total_price += sku.price*int(count)
# 更新成功之后跳出循环
break
# todo: 更新order的total_count和total_price
order.total_count = total_count
order.total_price = total_price
order.save()
except Exception as e:
transaction.savepoint_rollback(sid)
return JsonResponse({'res':7, 'errmsg':'下单失败'})
# 释放保存点
transaction.savepoint_commit(sid)
# todo: 删除购物车对应记录信息
conn.hdel(cart_key, *sku_ids) # 1,4
# 返回应答
return JsonResponse({'res':5, 'message':'订单创建成功'})
- 还有一种办法就是使用redis分布式锁,如何使用这里不再详细讲解
- 总结:
- 并发量高的时候使用悲观锁,缺点:加锁消耗资源
- 并发量低的时候使用乐观锁,缺点:乐观锁循环耗费时间(使用乐观锁的时候记得将DB隔离级别改为 ‘读提交’ )