基于Django的乐观锁与悲观锁解决订单并发问题的一点浅见
订单并发这个问题我想大家都是有一定认识的,这里我说一下我的一些浅见,我会尽可能的让大家了解如何解决这类问题。
在解释如何解决订单并发问题之前,需要先了解一下什么是数据库的事务。(我用的是mysql数据库,这里以mysql为例)
- 事务概念
一组mysql语句,要么执行,要么全不不执行。
- mysql事务隔离级别
Read Committed(读取提交内容)
如果是Django2.0以下的版本,需要去修改到这个隔离级别,不然乐观锁操作时无法读取已经被修改的数据
RepeatableRead(可重读)
这是这是Mysql默认的隔离级别,可以到mysql的配置文件中去修改;
transcation-isolation = READ-COMMITTED
在mysql配置文件中添加这行然后重启mysql就可以将事务隔离级别修改至Read Committed
其他事务知识这里不会用到就不浪费时间去做介绍了。
悲观锁:开启事务,然后给mysql的查询语句最后加上for update。
这是在干什么呢。可能大家有些不理解,其实就是给资源加上和多线程中加互斥锁一样的东西,确保在一个事务结束之前,别的事务无法对该数据进行操作。
下面是悲观锁的代码,加锁和解锁都是需要消耗CPU资源的,所以在订单并发少的情况使用乐观锁会是一个更好的选择。
'''
遇到问题没人解答?小编创建了一个Python学习交流QQ群:857662006 寻找有志同道合的小伙伴,
互帮互助,群里还有不错的视频学习教程和PDF电子书!
'''
class OrderCommitView(View):"""悲观锁"""# 开启事务装饰器@transaction.atomicdef post(self,request):"""订单并发 ———— 悲观锁"""# 拿到商品idgoods_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')# 拼接keycart_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_priceorder_info.save()# 事务提交transaction.commit()return JsonResponse({'res':5,'errmsg':'订单创建成功'})
然后就是乐观锁查询了,相比悲观锁,乐观锁其实并不能称为是锁,那么它是在做什么事情呢。
其实是在你要进行数据库操作时先去查询一次数据库中商品的库存,然后在你要更新数据库中商品库存时,将你一开始查询到的库存数量和商品的ID一起作为更新的条件,当受影响行数返回为0时,说明没有修改成功,那么就是说别的进程修改了该数据,那么你就可以回滚到之前没有进行数据库操作的时候,重新查询,重复之前的操作一定次数,如果超过你设置的次数还是不能修改那么就直接返回错误结果。
该方法只适用于订单并发较少的情况,如果失败次数过多,会带给用户不良体验,同时适用该方法要注意数据库的隔离级别一定要设置为Read Committed 。
最好在使用乐观锁之前查看一下数据库的隔离级别,mysql中查看事物隔离级别的命令为
select @@global.tx_isolation;
乐观锁代码
class OrderCommitView(View):"""乐观锁"""# 开启事务装饰器@transaction.atomicdef post(self,request):"""订单并发 ———— 乐观锁"""# 拿到idgoods_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')# 拼接keycart_key = 'cart_%d' % request.user.idfor 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.stockprint(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 - countnew_sales = goods.sales + countres = 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':'下单失败'})continueelse:breakOrderGoods.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_priceorder_info.save()# 事务提交transaction.savepoint_commit(sid)return JsonResponse({'res':6,'errmsg':'订单创建成功'})
基于Django的乐观锁与悲观锁解决订单并发问题的一点浅见相关推荐
- [初级]深入理解乐观锁与悲观锁
2019独角兽企业重金招聘Python工程师标准>>> 在数据库的锁机制中介绍过,数据库管理系统(DBMS)中的并发控制的任务是确保在多个事务同时存取数据库中同一数据时不破坏事务的隔 ...
- [精选]MySQL的各种锁(表锁,行锁,悲观锁,乐观锁,间隙锁,死锁)
不少人在开发的时候,应该很少会注意到这些锁的问题,也很少会给程序加锁(除了库存这些对数量准确性要求极高的情况下),即使我们不会这些锁知识,我们的程序在一般情况下还是可以跑得好好的.因为数据库隐式帮我们 ...
- 深入理解乐观锁与悲观锁
在数据库的锁机制中介绍过,数据库管理系统(DBMS)中的并发控制的任务是确保在多个事务同时存取数据库中同一数据时不破坏事务的隔离性和统一性以及数据库的统一性. 乐观并发控制(乐观锁)和悲观并发控制(悲 ...
- **Java有哪些悲观锁的实现_面试4连问:乐观锁与悲观锁的概念、实现方式、场景、优缺点?...
推荐阅读: 数据库面试4连问:分库分表,中间件,优缺点,如何拆分? 终极手撕之架构大全:分布式+框架+微服务+性能优化,够不够? 消息队列面试,你能顶得住面试官这波10大连环炮的攻势吗? 01 乐观锁 ...
- 悲观锁和乐观锁_乐观锁和悲观锁 以及 乐观锁的一种实现方式-CAS
悲观锁 总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞知道它拿到锁.传统的关系型数据库里面就用到了很多的这种锁机制,比如行锁,表锁等 ...
- MySQL 乐观锁与悲观锁
悲观锁 悲观锁(Pessimistic Lock),顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁. 悲观锁: ...
- 乐观锁与悲观锁以及乐观锁的一种实现方式-CAS
首先介绍一些乐观锁与悲观锁: 悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁.传统的关系型数据库里边就用到了很 ...
- 一篇文章带你解析,乐观锁与悲观锁的优缺点
乐观锁与悲观锁 概述 乐观锁 总是假设最好的情况,每次去读数据的时候都认为别人不会修改,所以不会上锁, 但是在更新的时候会判断一下在此期间有没有其他线程更新该数据, 可以使用版本号机制和CAS算法实现 ...
- 数据库中的乐观锁与悲观锁详解
目录 悲观锁 乐观锁 悲观锁实现方式 乐观锁实现方式 如何选择 悲观锁 当我们要对一个数据库中的一条数据进行修改的时候,为了避免同时被其他人修改,最好的办法就是直接对该数据进行加锁以防止并发. 这种借 ...
最新文章
- 重磅!谷歌面试官亲自分享:Google面试技巧
- fanuc机器人与plc的通讯_S7-1200PLC与FANUC机器人Profinet通讯方法
- 【PL/SQL的优点】
- 栈的应用——迷宫的非递归解法
- LIVE555再学习 -- DM368/Hi3516A 交叉编译
- jdbc mysql 报错 ssl_Mybatis使用JDBC连接数据库报错及解决方案
- FileUploadUtil
- 【转载】数据库操作:添加、插入、更新语句
- [ASP.NET Core 3框架揭秘] 跨平台开发体验: Docker
- WORD网址单词自动换行留下大量空白区?
- SpringBoot2.1.5(34)--- SpringBoot 实例
- 马云向日本捐赠100万只口罩:这是许多中国人的心意!
- Mego(05) - Mego Tools使用教程
- 【转】数字签名与数字证书
- Atitit.500 503 404错误处理最佳实践oak
- 2022年上半年软考开始报名啦
- 咕咕数据港股实时行情数据
- 存储、读取、清除cookies数据
- jQuery之文档就绪事件
- 关闭Win10系统天气图标