基于Django的乐观锁与悲观锁解决订单并发问题详解

作者:躬耕于数 时间:2021-07-14 19:42:08 

前言

订单并发这个问题我想大家都是有一定认识的,这里我说一下我的一些浅见,我会尽可能的让大家了解如何解决这类问题。

在解释如何解决订单并发问题之前,需要先了解一下什么是数据库的事务。(我用的是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资源的,所以在订单并发少的情况使用乐观锁会是一个更好的选择。


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':'订单创建成功'})

来源:https://blog.csdn.net/qq_36012543/article/details/79679690

标签:django,乐观,悲观,锁,订单,并发,问题
0
投稿

猜你喜欢

  • oracle单库彻底删除干净的执行步骤

    2024-01-21 13:01:05
  • python 通过麦克风录音 生成wav文件的方法

    2023-03-21 05:27:50
  • 浅谈keras使用预训练模型vgg16分类,损失和准确度不变

    2021-05-25 18:23:40
  • Python 类方法和实例方法(@classmethod),静态方法(@staticmethod)原理与用法分析

    2023-09-16 04:58:30
  • 基于Python的OpenCV骨架化图像并显示(skeletonize)

    2021-04-29 17:56:21
  • Python应用之利用pyecharts画中国地图

    2023-05-27 16:45:40
  • CSS布局之浮动(二)三列浮动

    2008-08-19 12:47:00
  • pytorch自定义不可导激活函数的操作

    2022-07-05 10:09:13
  • 解决MySQL数据库链接超时报1129错误问题

    2024-01-17 12:15:04
  • python实现逻辑回归的示例

    2022-04-05 05:16:59
  • 编写SQL需要注意的细节Checklist总结

    2012-10-07 10:43:57
  • 使用python实现http及ftp服务进行数据传输的方法

    2021-08-26 01:20:19
  • linux mysql5.5升级至mysql5.7的步骤与踩到的坑

    2024-01-21 17:09:08
  • Python脚本完成post接口测试的实例

    2022-01-01 15:40:22
  • sql server update 表的问题

    2009-10-04 20:32:00
  • jsp+ajax实现无刷新上传文件的方法

    2024-04-17 10:39:30
  • Django models文件模型变更错误解决

    2021-05-14 11:02:10
  • python同义词替换的实现(jieba分词)

    2022-02-10 20:58:00
  • Javascript中的基本类型和引用类型概述说明

    2024-04-18 09:37:04
  • python实现kNN算法

    2023-01-24 13:58:06
  • asp之家 网络编程 m.aspxhome.com