基于Django的乐观锁与悲观锁解决订单并发问题详解
前言
订单并发这个问题我想大家都是有一定认识的,这里我说一下我的一些浅见,我会尽可能的让大家了解如何解决这类问题。
在解释如何解决订单并发问题之前,需要先了解一下什么是数据库的事务。(我用的是mysql数据库,这里以mysql为例)
1) 事务概念
一组mysql语句,要么执行,要么全不不执行。
2) mysql事务隔离级别
ReadCommitted(读取提交内容)
如果是Django2.0以下的版本,需要去修改到这个隔离级别,不然乐观锁操作时无法读取已经被修改的数据
RepeatableRead(可重读)
这是这是Mysql默认的隔离级别,可以到mysql的配置文件中去修改;
transcation-isolation=READ-COMMITTED
在mysql配置文件中添加这行然后重启mysql就可以将事务隔离级别修改至ReadCommitted
其他事务知识这里不会用到就不浪费时间去做介绍了。
悲观锁:开启事务,然后给mysql的查询语句最后加上forupdate。
这是在干什么呢。可能大家有些不理解,其实就是给资源加上和多线程中加互斥锁一样的东西,确保在一个事务结束之前,别的事务无法对该数据进行操作。
下面是悲观锁的代码,加锁和解锁都是需要消耗CPU资源的,所以在订单并发少的情况使用乐观锁会是一个更好的选择。
classOrderCommitView(View):
"""悲观锁"""
#开启事务装饰器
@transaction.atomic
defpost(self,request):
"""订单并发————悲观锁"""
#拿到商品id
goods_ids=request.POST.getlist('goods_ids')
#校验参数
iflen(goods_ids)==0:
returnJsonResponse({'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)
exceptAddress.DoesNotExist:
returnJsonResponse({'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
)
forgoods_idingoods_ids:
#尝试查询商品
#此处考虑订单并发问题,
try:
#goods=Goods.objects.get(id=goods_id)#不加锁查询
goods=Goods.objects.select_for_update().get(id=goods_id)#加互斥锁查询
exceptGoodsgoods.DoesNotExist:
#回滚到保存点
transaction.rollback(sid)
returnJsonResponse({'res':2,'errmsg':'商品信息错误'})
#取出商品数量
count=conn.hget(cart_key,goods_id)
ifcountisNone:
#回滚到保存点
transaction.rollback(sid)
returnJsonResponse({'res':3,'errmsg':'商品不在购物车中'})
count=int(count)
ifgoods.stock
然后就是乐观锁查询了,相比悲观锁,乐观锁其实并不能称为是锁,那么它是在做什么事情呢。
其实是在你要进行数据库操作时先去查询一次数据库中商品的库存,然后在你要更新数据库中商品库存时,将你一开始查询到的库存数量和商品的ID一起作为更新的条件,当受影响行数返回为0时,说明没有修改成功,那么就是说别的进程修改了该数据,那么你就可以回滚到之前没有进行数据库操作的时候,重新查询,重复之前的操作一定次数,如果超过你设置的次数还是不能修改那么就直接返回错误结果。
该方法只适用于订单并发较少的情况,如果失败次数过多,会带给用户不良体验,同时适用该方法要注意数据库的隔离级别一定要设置为ReadCommitted。
最好在使用乐观锁之前查看一下数据库的隔离级别,mysql中查看事物隔离级别的命令为
select@@global.tx_isolation;
classOrderCommitView(View):
"""乐观锁"""
#开启事务装饰器
@transaction.atomic
defpost(self,request):
"""订单并发————乐观锁"""
#拿到id
goods_ids=request.POST.get('goods_ids')
iflen(goods_ids)==0:
returnJsonResponse({'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)
exceptAddress.DoesNotExist:
returnJsonResponse({'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
forgoods_idingoods_ids:
#尝试查询商品
#此处考虑订单并发问题,
#redis中取出商品数量
count=goods.hget(cart_key,goods_id)
ifcountisNone:
#回滚到保存点
transaction.savepoint_rollback(sid)
returnJsonResponse({'res':3,'errmsg':'商品不在购物车中'})
count=int(count)
foriinrange(3):
#若存在订单并发则尝试下单三次
try:
goods=Goodsgoods.objects.get(id=goods_id)#不加锁查询
#goods=Goodsgoods.objects.select_for_update().get(id=goods_id)#加互斥锁查询
exceptGoodsgoods.DoesNotExist:
#回滚到保存点
transaction.savepoint_rollback(sid)
returnJsonResponse({'res':2,'errmsg':'商品信息错误'})
origin_stock=goods.stock
print(origin_stock,'stock')
print(goods.id,'id')
iforigin_stock
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。