django 中乐观锁和悲观锁的使用 代码说明

前言

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

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


            
            

本博客源码Github地址:

https://github.com/whisnos/myblog

请随手给个star,谢谢!

打赏

评论