zoukankan      html  css  js  c++  java
  • 乐观锁和悲观锁 订单

     

     

    Django的乐观锁与悲观锁实现

    1.事务概念

    一组mysql语句,要么执行,要么全不不执行。

    2.mysql事务隔离级别

    https://www.jianshu.com/p/a9ca6a5d1348

     1.Read Committed(读取提交内容)

       如果是Django2.0以下的版本,需要去修改到这个隔离级别,不然乐观锁操作时无法读取已经被修改的数据

      2.RepeatableRead(可重读)

         这是这是Mysql默认的隔离级别,可以到mysql的配置文件中去修改;

         transcation-isolation = READ-COMMITTED

        在mysql配置文件中添加这行然后重启mysql就可以将事务隔离级别修改至Read Committed

    其他事务知识这里不会用到就不浪费时间去做介绍了。

    3.悲观锁:开启事务,然后给mysql的查询语句最后加上for update。

    transaction-isolati on=Read-Committed
    
    select @@g1oba1.tx_ isolation;

    这是在干什么呢。可能大家有些不理解,其实就是给资源加上和多线程中加互斥锁一样的东西,确保在一个事务结束之前,别的事务无法对该数据进行操作。

    下面是悲观锁的代码,加锁和解锁都是需要消耗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':'订单创建成功'})
    复制代码

    4.然后就是乐观锁查询了,相比悲观锁,乐观锁其实并不能称为是锁,那么它是在做什么事情呢。

    其实是在你要进行数据库操作时先去查询一次数据库中商品的库存,然后在你要更新数据库中商品库存时,将你一开始查询到的库存数量和商品的ID一起作为更新的条件,当受影响行数返回为0时,说明没有修改成功,那么就是说别的进程修改了该数据,那么你就可以回滚到之前没有进行数据库操作的时候,重新查询,重复之前的操作一定次数,如果超过你设置的次数还是不能修改那么就直接返回错误结果。

    该方法只适用于订单并发较少的情况,如果失败次数过多,会带给用户不良体验,同时适用该方法要注意数据库的隔离级别一定要设置为Read Committed 。

    复制代码
    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':'订单创建成功'})
     
  • 相关阅读:
    encodeURI() 的用法
    $().each() 与 $.each()区别,以及 jquery ajax 应用
    每日一乐,健康多滋味~~
    IIS部署ASP.NET MVC (4.0)网站出现的错误
    《程序员级别鉴定书》 ----中级.NET开发者
    《转》程序员必须知道的10大基础实用算法及其讲解
    C# 托管资源和非托管资源
    11、E-commerce in Your Inbox:Product Recommendations at Scale-----产品推荐(prod2vec和user2vec)
    二叉树(2)----路径
    10、Latent Relational Metric Learning via Memory-based Attention for Collaborative Ranking-----基于记忆注意的潜在关系度量协同排序
  • 原文地址:https://www.cnblogs.com/tfzz/p/12604251.html
Copyright © 2011-2022 走看看