侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

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

框架(架构)  /  管理员 发布于 7年前   239

前言

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

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

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


  • 上一条:
    Django上使用数据可视化利器Bokeh解析
    下一条:
    django解决订单并发问题【推荐】
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • Filament v3.1版本发布(0个评论)
    • docker + gitea搭建一个git服务器流程步骤(0个评论)
    • websocket的三种架构方式使用优缺点浅析(0个评论)
    • ubuntu20.4系统中宿主机安装nginx服务,docker容器中安装php8.2实现运行laravel10框架网站(0个评论)
    • phpstudy_pro(小皮面板)中安装最新php8.2.9版本流程步骤(0个评论)
    • 近期文章
    • 在go中实现一个常用的先进先出的缓存淘汰算法示例代码(0个评论)
    • 在go+gin中使用"github.com/skip2/go-qrcode"实现url转二维码功能(0个评论)
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • Laravel从Accel获得5700万美元A轮融资(0个评论)
    • 在go + gin中gorm实现指定搜索/区间搜索分页列表功能接口实例(0个评论)
    • 在go语言中实现IP/CIDR的ip和netmask互转及IP段形式互转及ip是否存在IP/CIDR(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2018-05
    • 2020-02
    • 2020-03
    • 2020-05
    • 2020-06
    • 2020-07
    • 2020-08
    • 2020-11
    • 2021-03
    • 2021-09
    • 2021-10
    • 2021-11
    • 2022-01
    • 2022-02
    • 2022-03
    • 2022-08
    • 2023-08
    • 2023-10
    • 2023-12
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客