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

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

本文来自:

https://blog.csdn.net/qq_36012543/article/details/79679690?utm_source=copy

-END-


识别图中二维码,领取python全套视频资料

基于Django的乐观锁与悲观锁解决订单并发问题的一点浅见的更多相关文章

  1. Hibernate事务与并发问题处理(乐观锁与悲观锁)

    目录 一.数据库事务的定义 二.数据库事务并发可能带来的问题 三.数据库事务隔离级别 四.使用Hibernate设置数据库隔离级别 五.使用悲观锁解决事务并发问题 六.使用乐观锁解决事务并发问题 Hi ...

  2. mysql的锁--行锁,表锁,乐观锁,悲观锁

    一 引言--为什么mysql提供了锁 最近看到了mysql有行锁和表锁两个概念,越想越疑惑.为什么mysql要提供锁机制,而且这种机制不是一个摆设,还有很多人在用.在现代数据库里几乎有事务机制,aci ...

  3. 【数据库】mysql深入理解乐观锁与悲观锁

    转载:http://www.hollischuang.com/archives/934 在数据库的锁机制中介绍过,数据库管理系统(DBMS)中的并发控制的任务是确保在多个事务同时存取数据库中同一数据时 ...

  4. [数据库锁机制] 深入理解乐观锁、悲观锁以及CAS乐观锁的实现机制原理分析

    前言: 在并发访问情况下,可能会出现脏读.不可重复读和幻读等读现象,为了应对这些问题,主流数据库都提供了锁机制,并引入了事务隔离级别的概念.数据库管理系统(DBMS)中的并发控制的任务是确保在多个事务 ...

  5. 乐观锁vs悲观锁

    引言 为什么需要锁(并发控制) 在并发的环境中,会存在多个用户同时更新同一条数据,这时就会产生冲突. 冲突结果: 丢失更新:一个事务的更新覆盖了其它事务的更新结果,就是所谓的更新丢失. 脏读:当一个事 ...

  6. 多线程深入:乐观锁与悲观锁以及乐观锁的一种实现方式-CAS(转)

    原文:https://www.cnblogs.com/qjjazry/p/6581568.html 首先介绍一些乐观锁与悲观锁: 悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每 ...

  7. Mysql乐观锁与悲观锁

    乐观锁和悲观锁是两种常见的资源并发锁设计思路,也是并发编程中一个非常重要的基础理念. Mysql的悲观锁 什么是悲观锁(Pessimistic Lock): 悲观锁的特点是先获取锁,再进行业务操作,即 ...

  8. Java并发问题--乐观锁与悲观锁以及乐观锁的一种实现方式-CAS

    首先介绍一些乐观锁与悲观锁: 悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁.传统的关系型数据库里边就用到了很 ...

  9. 乐观锁与悲观锁以及乐观锁的一种实现方式-CAS

    首先介绍一些乐观锁与悲观锁: 悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁.传统的关系型数据库里边就用到了很 ...

随机推荐

  1. xmapp+netbeans调试

    1.netbeans的设置 2.修改php.ini 如下: [XDebug]zend_extension = "D:\dev\xampp\php\ext\php_xdebug.dll&quo ...

  2. PHP——分页显示数据库内容

    test.php <?php header("Content-Type:text/html;charset=utf-8"); //加载分页类 include "pa ...

  3. winxp退市是微软windows操作系统的滑铁卢

    winxp退市是微软windows操作系统的滑铁卢 兵败如山倒,windowsxp退市.宣布微软时代结束,windows将逐步退出中国市场,取而代之将是中国人自己的操作系统.中国人努力.中国人加油,不 ...

  4. TensorFlow基础笔记(2) minist分类学习

    (1) 最简单的神经网络分类器 # encoding: UTF-8 import tensorflow as tf from tensorflow.examples.tutorials.mnist i ...

  5. DOM中XMLDOMnodelist的length属性的表示是:(选择1项)

    DOM中XMLDOMnodelist的length属性的表示是:(选择1项) A. 该对象中文本字符的长度 B. 该对象中元素节点的数量 C. 该对象中节点的数量 D. 该对象中文档对象的数量 解答: ...

  6. bootstrap基础学习四篇

    bootstrap代码 Bootstrap 允许两种方式显示代码: 第一种是 <code> 标签.如果您想要内联显示代码,那么您应该使用 <code> 标签. 第二种是 < ...

  7. ES5与ES6的继承

    JavaScript本身是一种神马语言: 提到继承,我们常常会联想到C#.java等面向对象的高级语言(当然还有C++),因为存在类的概念使得这些语言在实际的使用中抽象成为一个对象,即面向对象.Jav ...

  8. MyBitis(iBitis)系列随笔之六:mybitis与spring集成

    目前Spring官方还没有出整合Mybatis的特性,但是mybitis比较给力,开发了一个mybatis-spring插件,达到与Spring的完美整合目的. 在与Spring集成前,一方面我们需要 ...

  9. a &a &a[0]之间的区别和联系

    数组中,a为数组的首地址,&a[0]为数组第一个元素的地址. 所以 a == &a[0] 但是,&a又是什么东西呢? 我们来做下面的代码测试: #include <std ...

  10. fopen与读写的标识r,r+,rb+,rt+,w+.....

    FILE * fopen(const char * path,const char * mode); 参数mode字符串则代表着流形态. mode有下列几种形态字符串: r 打开只读文件,该文件必须存 ...