Django解决订单并发:悲观锁与乐观锁实战解析
本文详细解析Django中处理订单并发的两种核心策略:悲观锁与乐观锁。通过对比分析,介绍了MySQL事务隔离级别对乐观锁的影响,并提供了基于Django ORM的完整代码示例。重点讲解了悲观锁使用for update加锁机制,以及乐观锁通过版本号或库存条件更新实现无锁并发控制的原理与适用场景。
Django解决订单并发:悲观锁与乐观锁实战解析
前言
订单并发问题在电商系统中十分常见,本文旨在分享个人对解决此类问题的理解,力求帮助读者掌握相关的处理方案。
数据库事务基础与隔离级别配置
在探讨具体解决方案前,有必要先回顾数据库事务的基本概念。本文以MySQL数据库为例进行说明。
1) 事务概念
事务是指一组MySQL语句的执行单元,其特性是“要么全部执行成功,要么全部不执行”,确保数据的一致性。
2) mysql事务隔离级别
Read Committed(读取已提交内容)
若使用的Django版本低于2.0,需将数据库隔离级别调整为Read Committed,否则在乐观锁场景下,事务无法读取已被其他事务修改的数据,导致逻辑错误。
Repeatable Read(可重复读)
这是MySQL默认的隔离级别。如需修改,可进入MySQL配置文件进行设置。
transcation-isolation = READ-COMMITTED
在MySQL配置文件中添加上述配置行,随后重启MySQL服务,即可将事务隔离级别修改为Read Committed。
其他事务相关知识在此处不再赘述,以免分散注意力。
悲观锁实现机制与代码示例
悲观锁的实现方式是:开启事务后,在MySQL查询语句末尾添加“for update”关键字。
这一操作的作用类似于多线程环境中的互斥锁,旨在确保在事务结束前,其他事务无法对该数据进行修改或读取,从而保证数据的一致性。
以下是悲观锁的实现代码。需要注意的是,加锁和解锁过程会消耗CPU资源,因此在订单并发量较低的场景下,使用乐观锁通常是更优的选择。
class OrderCommitView(View):
"""悲观锁"""
# 开启事务装饰器
@transaction.atomic
def post(self,request):
"""订单并发 ———— 悲观锁"""
# 拿到商品id
goods_ids = request.POST.getlist('goods_ids')
# 校验参数
if len(goods_ids) == 0 :
return JsonResponse({'res':0,'errmsg':'数据不完整'})
# 当前时间字符串
now_str = datetime.now().strftime('%Y%m%d%H%M%S')
# 订单编号
order_id = now_str + str(request.user.id)
# 地址
pay_method = request.POST.get('pay_method')
# 支付方式
address_id = request.POST.get('address_id')
try:
address = Address.objects.get(id=address_id)
except Address.DoesNotExist:
return JsonResponse({'res':1,'errmsg':'地址错误'})
# 商品数量
total_count = 0
# 商品总价
total_amount = 0
# 获取redis连接
conn = get_redis_connection('default')
# 拼接key
cart_key = 'cart_%d' % request.user.id
#
# 创建保存点
sid = transaction.savepoint()
order_info = OrderInfo.objects.create(
order_id = order_id,
user = request.user,
addr = address,
pay_method = pay_method,
total_count = total_count,
total_price = total_amount
)
for goods_id in goods_ids:
# 尝试查询商品
# 此处考虑订单并发问题,
try:
# goods = Goods.objects.get(id=goods_id) # 不加锁查询
goods = Goods.objects.select_for_update().get(id=goods_id) # 加互斥锁查询
except Goodsgoods.DoesNotExist:
# 回滚到保存点
transaction.rollback(sid)
return JsonResponse({'res':2,'errmsg':'商品信息错误'})
# 取出商品数量
count = conn.hget(cart_key,goods_id)
if count is None:
# 回滚到保存点
transaction.rollback(sid)
return JsonResponse({'res':3,'errmsg':'商品不在购物车中'})
count = int(count)
if goods.stock < count:
# 回滚到保存点
transaction.rollback(sid)
return JsonResponse({'res':4,'errmsg':'库存不足'})
# 商品销量增加
goods.sales += count
# 商品库存减少
goods.stock -= count
# 保存到数据库
goods.save()
OrderGoods.objects.create(
order = order_info,
goods = goods,
count = count,
price = goods.price
)
# 累加商品件数
total_count += count
# 累加商品总价
total_amount += (goods.price) * count
# 更新订单信息中的商品总件数
order_info.total_count = total_count
# 更新订单信息中的总价格
order_info.total_price = total_amount + order_info.transit_price
order_info.save()
# 事务提交
transaction.commit()
return JsonResponse({'res':5,'errmsg':'订单创建成功'})
乐观锁工作原理与重试策略
接下来介绍乐观锁机制。与悲观锁不同,乐观锁实际上并不加锁,那么它具体是如何工作的呢?
其核心逻辑是:在执行数据库更新操作前,先查询一次商品的当前库存。在更新库存时,将初始查询到的库存数量以及商品ID共同作为更新条件。如果受影响行数为0,说明数据已被其他进程修改,此时应回滚事务至操作前的保存点,重新查询并再次尝试更新。该过程可重复指定次数,若超过设定次数仍无法修改,则直接返回错误结果。
该方法仅适用于订单并发量较少的场景。如果失败重试次数过多,会严重影响用户体验。此外,使用此方法时必须确保数据库隔离级别已设置为Read Committed。
建议在使用乐观锁前,先检查数据库的隔离级别。在MySQL中,查看事务隔离级别的命令为
select @@global.tx_isolation;
class OrderCommitView(View):
"""乐观锁"""
# 开启事务装饰器
@transaction.atomic
def post(self,request):
"""订单并发 ———— 乐观锁"""
# 拿到id
goods_ids = request.POST.get('goods_ids')
if len(goods_ids) == 0 :
return JsonResponse({'res':0,'errmsg':'数据不完整'})
# 当前时间字符串
now_str = datetime.now().strftime('%Y%m%d%H%M%S')
# 订单编号
order_id = now_str + str(request.user.id)
# 地址
pay_method = request.POST.get('pay_method')
# 支付方式
address_id = request.POST.get('address_id')
try:
address = Address.objects.get(id=address_id)
except Address.DoesNotExist:
return JsonResponse({'res':1,'errmsg':'地址错误'})
# 商品数量
total_count = 0
# 商品总价
total_amount = 0
# 订单运费
transit_price = 10
# 创建保存点
sid = transaction.savepoint()
order_info = OrderInfo.objects.create(
order_id = order_id,
user = request.user,
addr = address,
pay_method = pay_method,
total_count = total_count,
total_price = total_amount,
transit_price = transit_price
)
# 获取redis连接
goods = get_redis_goodsection('default')
# 拼接key
cart_key = 'cart_%d' % request.user.id
for goods_id in goods_ids:
# 尝试查询商品
# 此处考虑订单并发问题,
# redis中取出商品数量
count = goods.hget(cart_key, goods_id)
if count is None:
# 回滚到保存点
transaction.savepoint_rollback(sid)
return JsonResponse({'res': 3, 'errmsg': '商品不在购物车中'})
count = int(count)
for i in range(3):
# 若存在订单并发则尝试下单三次
try:
goods = Goodsgoods.objects.get(id=goods_id) # 不加锁查询
# goods = Goodsgoods.objects.select_for_update().get(id=goods_id) # 加互斥锁查询
except Goodsgoods.DoesNotExist:
# 回滚到保存点
transaction.savepoint_rollback(sid)
return JsonResponse({'res':2,'errmsg':'商品信息错误'})
origin_stock = goods.stock
print(origin_stock, 'stock')
print(goods.id, 'id')
if origin_stock < count:
# 回滚到保存点
transaction.savepoint_rollback(sid)
return JsonResponse({'res':4,'errmsg':'库存不足'})
# # 商品销量增加
# goods.sales += count
# # 商品库存减少
# goods.stock -= count
# # 保存到数据库
# goods.save()
# 如果下单成功后的库存
new_stock = goods.stock - count
new_sales = goods.sales + count
res = Goodsgoods.objects.filter(stock=origin_stock,id=goods_id).update(stock=new_stock,sales=new_sales)
print(res)
if res == 0:
if i == 2:
# 回滚
transaction.savepoint_rollback(sid)
return JsonResponse({'res':5,'errmsg':'下单失败'})
continue
else:
break
OrderGoods.objects.create(
order = order_info,
goods = goods,
count = count,
price = goods.price
)
# 删除购物车中记录
goods.hdel(cart_key,goods_id)
# 累加商品件数
total_count += count
# 累加商品总价
total_amount += (goods.price) * count
# 更新订单信息中的商品总件数
order_info.total_count = total_count
# 更新订单信息中的总价格
order_info.total_price = total_amount + order_info.transit_price
order_info.save()
# 事务提交
transaction.savepoint_commit(sid)
return JsonResponse({'res':6,'errmsg':'订单创建成功'})
以上就是本文的全部内容,希望能为您的学习提供帮助,也欢迎大家继续支持本站。
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













