基于Django的乐观锁与悲观锁解决订单并发问题详解
前言
订单并发这个问题我想大家都是有一定认识的,这里我说一下我的一些浅见,我会尽可能的让大家了解如何解决这类问题。
在解释如何解决订单并发问题之前,需要先了解一下什么是数据库的事务。(我用的是mysql数据库,这里以mysql为例)
1) 事务概念
一组mysql语句,要么执行,要么全不不执行。
2) mysql事务隔离级别
Read Committed(读取提交内容)
如果是Django2.0以下的版本,需要去修改到这个隔离级别,不然乐观锁操作时无法读取已经被修改的数据
RepeatableRead(可重读)
这是这是Mysql默认的隔离级别,可以到mysql的配置文件中去修改;
transcation-isolation = READ-COMMITTED
在mysql配置文件中添加这行然后重启mysql就可以将事务隔离级别修改至Read Committed
其他事务知识这里不会用到就不浪费时间去做介绍了。
悲观锁:开启事务,然后给mysql的查询语句最后加上for update。
这是在干什么呢。可能大家有些不理解,其实就是给资源加上和多线程中加互斥锁一样的东西,确保在一个事务结束之前,别的事务无法对该数据进行操作。
下面是悲观锁的代码,加锁和解锁都是需要消耗CPU资源的,所以在订单并发少的情况使用乐观锁会是一个更好的选择。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | 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;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 | 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' : '订单创建成功' }) |
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。
微信公众号搜索 “ 脚本之家 ” ,选择关注
程序猿的那些事、送书等活动等着你
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权/违法违规/事实不符,请将相关资料发送至 reterry123@163.com 进行投诉反馈,一经查实,立即处理!
相关文章
Python 闭包,函数分隔作用域,nonlocal声明非局部变量操作示例
这篇文章主要介绍了Python 闭包,函数分隔作用域,nonlocal声明非局部变量操作,结合实例形式分析了Python闭包及闭包中的变量声明相关操作技巧,需要的朋友可以参考下2019-10-10
最新评论