python数据库并发处理(乐观锁)

1.数据库并发处理问题

在多个用户同时发起对同一个数据提交修改操作时(先查询,再修改),会出现资源竞争的问题,导致最终修改的数据结果出现异常。

比如限量商品在热销时,当多个用户同时请求购买商品时,最终修改的数据就会出现异常 
数据库并发处理

下面我们来写点代码还原一下现象:

1.新建项目Optimistic locking,创建应用app01,编辑models创建一张表并执行数据库迁移,如下:

from django.db import models

class GoodsInfo(models.Model):
    """ 商品 """
    name = models.CharField(max_length=50, verbose_name='名称')
    stock = models.IntegerField(default=0, verbose_name='库存')

    class Meta:
        db_table = 'tb_goodsinfo'

2.往数据库中插入一条数据:insert into tb_goodsinfo values(0, "macbook", 10);

3.定义Goods视图类,

  • 增加判断库存和修改库存之间的间隙,就可以模拟出A用户尚未修改库存之前,B用户已经开始进行判断库存,导致误差:
from django.http import HttpResponse
from rest_framework.generics import GenericAPIView
from app01.models import GoodsInfo

class Goods(GenericAPIView):
    """ 购买商品 """
    def post(self, request):
        # 获取请求头中查询字符串数据
        goods_id = request.GET.get('goods_id')
        count = int(request.GET.get('count'))

        # 查询商品对象
        goods = GoodsInfo.objects.filter(id=goods_id).first()
        # 获取原始库存
        origin_stock = goods.stock

        # 判断商品库存是否充足
        if origin_stock < count:
            return HttpResponse(content="商品库存不足", status=400)

        # 演示多个用户并发请求
        import time
        time.sleep(5)

        # 减少商品的库存数量,保存到数据库
        goods.stock = origin_stock - count
        goods.save()

        return HttpResponse(content="操作成功", status=200)
  •  
from django.conf.urls import url
from . import views

urlpatterns =[
    url(r'^goods/$', views.Goods.as_view()),
]

我们先使用postman来模拟单个用户请求

postman

  • 再来查询数据库,单个用户请求正常,(将stock恢复到10) 
    查询数据库

模拟多个用户请求

我们来使用两个postman模拟A,B用户同时请求,用户A买6套商品,用户B买5套商品

运行结果:

  • 输出日志: 
    log
  • 查询数据库: 
    查询数据库
  • 两个postman发出的post请求均提示 “操作成功”

分析及结论:

  • 当A用户请求的时候,goods.stock = origin_stock - count 
    A操作的结果:goods.stock = 10 - 6 = 4

  • 可是B用户判断库存的时候,A还未将修改的数据保存到数据库,所以B获取的库存数量也是 10 
    B操作的结果:goods.stock = 10 - 5 = 5

  • 写入数据库操作中,B的数据将A的数据覆盖,故最后的库存还是 5

2.解决办法:

如果使用给数据库加锁的方式,在给处理多个商品时可能会出现死锁,所以使用数据库中的乐观锁方式来处理效果较好

数据库乐观锁: 
乐观锁并不是真实存在的锁,而是在更新的时候判断此时的库存是否是之前查询出的库存,如果相同,表示没人修改,可以更新库存,否则表示别人抢过资源,不再执行库存更新。类似如下操作

使用原生的SQL语句
update tb_goodsinfo set stock=5 where id=1 and stock=10;

使用Django中的语法
GoodsInfo.objects.filter(id=1, stock=10).update(stock=5)

# GoodsInfo:模型类,  id:商品id,  stock:库存

改写视图类:

from django.http import HttpResponse
from rest_framework.generics import GenericAPIView
from app01.models import GoodsInfo

class Goods(GenericAPIView):
    """ 购买商品 """
    def post(self, request):
        # 获取请求头中查询字符串数据
        goods_id = request.GET.get('goods_id')
        count = int(request.GET.get('count'))

        while True:
            # 查询商品对象
            goods = GoodsInfo.objects.filter(id=goods_id).first()
            # 获取原始库存
            origin_stock = goods.stock

            # 判断商品库存是否充足
            if origin_stock < count:
                return HttpResponse(content="商品库存不足", status=400)

            # 演示并发请求
            import time
            time.sleep(5)

            # 减少商品的库存数量,保存到数据库
            # goods.stock = origin_stock - count
            # goods.save()
            """ 使用乐观锁进行处理,一步完成数据库的查询和更新 """
            # update返回受影响的行数
            result = GoodsInfo.objects.filter(id=goods.id, stock=origin_stock).update(stock=origin_stock - count)
            if result == 0:
                # 表示更新失败,有人抢先购买了商品,重新获取库存信息,判断库存
                continue

            # 表示购买成功,退出 while 循环
            break

        return HttpResponse(content="操作成功", status=200)
  • 结果:
  • 输出日志: 
    log_cuccess
  • 查询数据库 
    查询数据库
  • A用户返回 “操作成功”, B用户返回 “商品库存不足”

3.需要修改MySQL的事务隔离级别

事务隔离级别指的是在处理同一个数据的多个事务中,一个事务修改数据后,其他事务何时能看到修改后的结果。

MySQL数据库事务隔离级别主要有四种:

Serializable 串行化,一个事务一个事务的执行

Repeatable read 可重复读,无论其他事务是否修改并提交了数据,在这个事务中看到的数据值始终不受其他事务影响

Read committed 读取已提交,其他事务提交了对数据的修改后,本事务就能读取到修改后的数据值

Read uncommitted 读取为提交,其他事务只要修改了数据,即使未提交,本事务也能看到修改后的数据值。

MySQL数据库默认使用可重复读( Repeatable read),而使用乐观锁的时候,如果一个事务修改了库存并提交了事务,那其他的事务应该可以读取到修改后的数据值,所以不能使用可重复读的隔离级别,应该修改为读取已提交Read committed。

修改方法:

  • 打开配置文件 
    打开mysql配置文件

  • 修改隔离级别 
    修改隔离级别

猜你喜欢

转载自blog.csdn.net/hello_mumu/article/details/82253456
今日推荐