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


Posted in Python onJuly 31, 2019

前言

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

在解释如何解决订单并发问题之前,需要先了解一下什么是数据库的事务。(我用的是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':'订单创建成功'})

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
在Windows系统上搭建Nginx+Python+MySQL环境的教程
Dec 25 Python
浅谈python numpy中nonzero()的用法
Apr 02 Python
Python图像处理之直线和曲线的拟合与绘制【curve_fit()应用】
Dec 26 Python
python把转列表为集合的方法
Jun 28 Python
TensorFlow基本的常量、变量和运算操作详解
Feb 03 Python
python程序文件扩展名知识点详解
Feb 27 Python
Python中remove漏删和索引越界问题的解决
Mar 18 Python
Python3读取和写入excel表格数据的示例代码
Jun 09 Python
Python基于内置函数type创建新类型
Oct 22 Python
python自动从arxiv下载paper的示例代码
Dec 05 Python
python 将html转换为pdf的几种方法
Dec 29 Python
Python 转移文件至云对象存储的方法
Feb 07 Python
django解决订单并发问题【推荐】
Jul 31 #Python
python opencv将图片转为灰度图的方法示例
Jul 31 #Python
Django中使用极验Geetest滑动验证码过程解析
Jul 31 #Python
Python对接六大主流数据库(只需三步)
Jul 31 #Python
Python爬虫 scrapy框架爬取某招聘网存入mongodb解析
Jul 31 #Python
python爬虫 模拟登录人人网过程解析
Jul 31 #Python
Python爬虫 bilibili视频弹幕提取过程详解
Jul 31 #Python
You might like
php数组声明、遍历、数组全局变量使用小结
2013/06/05 PHP
CheckBox 如何实现全选?
2006/06/23 Javascript
Prototype Date对象 学习
2009/07/12 Javascript
javascript中String类的subString()方法和slice()方法
2011/05/24 Javascript
页面使用密码保护代码
2013/04/10 Javascript
javascript验证上传文件的类型限制必须为某些格式
2013/11/14 Javascript
如何用jQuery实现ASP.NET GridView折叠伸展效果
2015/09/26 Javascript
EasyUI中在表单提交之前进行验证
2016/07/19 Javascript
Bootstrap选项卡学习笔记分享
2017/02/13 Javascript
微信小程序实现拖拽 image 触摸事件监听的实例
2017/08/17 Javascript
使用canvas实现一个vue弹幕组件功能
2018/11/30 Javascript
jQuery创建折叠式菜单
2019/06/15 jQuery
微信小程序-可移动菜单的实现过程详解
2019/06/24 Javascript
ES6 Class中实现私有属性的一些方法总结
2019/07/08 Javascript
Net微信网页开发 使用微信JS-SDK获取当前地理位置过程详解
2019/08/26 Javascript
Javascript查看大图功能代码实现
2020/05/07 Javascript
Javascript前端下载后台传来的文件流代码实例
2020/08/18 Javascript
Python yield使用方法示例
2013/12/04 Python
Python实现将n个点均匀地分布在球面上的方法
2015/03/12 Python
Python中的默认参数详解
2015/06/24 Python
python开发利器之ulipad的使用实践
2017/03/16 Python
Python 元类实例解析
2018/04/04 Python
Python3实现的简单三级菜单功能示例
2019/03/12 Python
Python pandas.DataFrame调整列顺序及修改index名的方法
2019/06/21 Python
Python调用C语言的实现
2019/07/26 Python
python中字典按键或键值排序的实现代码
2019/08/27 Python
selenium+超级鹰实现模拟登录12306
2021/01/24 Python
迟到检讨书300字
2014/02/14 职场文书
安全生产实施方案
2014/02/23 职场文书
寄语是什么意思
2014/04/10 职场文书
党政领导班子群众路线对照检查材料思想汇报
2014/09/27 职场文书
2015年师德师风自我评价范文
2015/03/05 职场文书
党支部鉴定意见
2015/06/02 职场文书
pytorch常用数据类型所占字节数对照表一览
2021/05/17 Python
Pytorch中Softmax与LogSigmoid的对比分析
2021/06/05 Python
Java并发编程必备之Future机制
2021/06/30 Java/Android