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

 更新时间:2019年07月31日 10:33:33   作者:躬耕于数  
这篇文章主要介绍了基于Django的乐观锁与悲观锁解决订单并发问题详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

Python客栈送红包、纸质书

前言

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
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;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
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':'订单创建成功'})

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

您可能感兴趣的文章:
蓄力AI

微信公众号搜索 “ 脚本之家 ” ,选择关注

程序猿的那些事、送书等活动等着你

原文链接:https://blog.csdn.net/qq_36012543/article/details/79679690

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权/违法违规/事实不符,请将相关资料发送至 reterry123@163.com 进行投诉反馈,一经查实,立即处理!

相关文章

  • Python爬虫实现爬取百度百科词条功能实例

    Python爬虫实现爬取百度百科词条功能实例

    这篇文章主要介绍了Python爬虫实现爬取百度百科词条功能,结合完整实例形式分析了Python爬虫的基本原理及爬取百度百科词条的步骤、网页下载、解析、数据输出等相关操作技巧,需要的朋友可以参考下
    2019-04-04
  • Python实现语音合成功能详解

    Python实现语音合成功能详解

    这篇文章主要为大家介绍了一个通过Python制作的小工具,可以实现语音识别以及文字转语音的功能,文中的实现步骤讲解详细,感兴趣的可以动手试一试
    2022-01-01
  • Python实现常见坐标系的相互转换

    Python实现常见坐标系的相互转换

    WGS84坐标系、GCJ02坐标系、BD09坐标系和Web 墨卡托投影坐标系是我们常见的四个坐标系。这篇文章为大家整理了这四个坐标系之间相互转换的方法,需要的可以参考一下
    2023-02-02
  • Python中数组切片的用法实例详解

    Python中数组切片的用法实例详解

    python的数组切片操作很强大,但有些细节老是忘,故写一点东西记录下来,下面这篇文章主要给大家介绍了关于Python中数组切片用法的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考下
    2022-12-12
  • fastcgi文件读取漏洞之python扫描脚本

    fastcgi文件读取漏洞之python扫描脚本

    这篇文章主要介绍了fastcgi文件读取漏洞之python扫描脚本,需要的朋友可以参考下
    2017-04-04
  • 深入理解Python虚拟机中魔术方法的使用

    深入理解Python虚拟机中魔术方法的使用

    这篇文章主要给大家介绍在 cpython 当中一些比较花里胡哨的魔术方法,以帮助我们自己实现比较花哨的功能,当然这其中也包含一些也非常实用的魔术方法,需要的可以参考下
    2023-05-05
  • Python机器学习库scikit-learn使用详解

    Python机器学习库scikit-learn使用详解

    scikit-learn是Python中最流行的机器学习库之一,它提供了各种各样的机器学习算法和工具,包括分类、回归、聚类、降维等
    2023-03-03
  • Python常见文件操作的示例详解

    Python常见文件操作的示例详解

    文件操作是我们开发中必不可少的一项需求。本文主要给大家介绍了关于Python常见的一些文件操作,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2022-07-07
  • 详解Python的Django框架中的通用视图

    详解Python的Django框架中的通用视图

    这篇文章主要介绍了详解Python的Django框架中的通用视图,是为MVC架构的Django框架下的基础知识,需要的朋友可以参考下
    2015-05-05
  • Python 闭包,函数分隔作用域,nonlocal声明非局部变量操作示例

    Python 闭包,函数分隔作用域,nonlocal声明非局部变量操作示例

    这篇文章主要介绍了Python 闭包,函数分隔作用域,nonlocal声明非局部变量操作,结合实例形式分析了Python闭包及闭包中的变量声明相关操作技巧,需要的朋友可以参考下
    2019-10-10

最新评论