赞
踩
1.数据库并发处理问题
在多个用户同时发起对同一个数据提交修改操作时(先查询,再修改),会出现资源竞争的问题,导致最终修改的数据结果出现异常。
比如限量商品在热销时,当多个用户同时请求购买商品时,最终修改的数据就会出现异常
下面我们来写点代码还原一下现象:
1.新建项目Optimistic locking,创建应用app01,编辑models创建一张表并执行数据库迁移,如下:
from django.db importmodelsclassGoodsInfo(models.Model):"""商品"""name= models.CharField(max_length=50, verbose_name='名称')
stock= models.IntegerField(default=0, verbose_name='库存')classMeta:
db_table= 'tb_goodsinfo'
2.往数据库中插入一条数据:insert into tb_goodsinfo values(0, "macbook", 10);
3.定义Goods视图类,
增加判断库存和修改库存之间的间隙,就可以模拟出A用户尚未修改库存之前,B用户已经开始进行判断库存,导致误差:
from django.http importHttpResponsefrom rest_framework.generics importGenericAPIViewfrom app01.models importGoodsInfoclassGoods(GenericAPIView):"""购买商品"""
defpost(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
importtime
time.sleep(5)#减少商品的库存数量,保存到数据库
goods.stock = origin_stock -count
goods.save()return HttpResponse(content="操作成功", status=200)
我们先使用postman来模拟单个用户请求
再来查询数据库,单个用户请求正常,(将stock恢复到10)
模拟多个用户请求
我们来使用两个postman模拟A,B用户同时请求,用户A买6套商品,用户B买5套商品
运行结果:
输出日志:
查询数据库:
两个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 importHttpResponsefrom rest_framework.generics importGenericAPIViewfrom app01.models importGoodsInfoclassGoods(GenericAPIView):"""购买商品"""
defpost(self, request):#获取请求头中查询字符串数据
goods_id = request.GET.get('goods_id')
count= int(request.GET.get('count'))whileTrue:#查询商品对象
goods = GoodsInfo.objects.filter(id=goods_id).first()#获取原始库存
origin_stock =goods.stock#判断商品库存是否充足
if origin_stock
importtime
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)
结果:
输出日志:
查询数据库
A用户返回 “操作成功”, B用户返回 “商品库存不足”
3.需要修改MySQL的事务隔离级别
事务隔离级别指的是在处理同一个数据的多个事务中,一个事务修改数据后,其他事务何时能看到修改后的结果。
MySQL数据库事务隔离级别主要有四种:
Serializable 串行化,一个事务一个事务的执行
Repeatable read 可重复读,无论其他事务是否修改并提交了数据,在这个事务中看到的数据值始终不受其他事务影响
Read committed 读取已提交,其他事务提交了对数据的修改后,本事务就能读取到修改后的数据值
Read uncommitted 读取为提交,其他事务只要修改了数据,即使未提交,本事务也能看到修改后的数据值。
MySQL数据库默认使用可重复读( Repeatable read),而使用乐观锁的时候,如果一个事务修改了库存并提交了事务,那其他的事务应该可以读取到修改后的数据值,所以不能使用可重复读的隔离级别,应该修改为读取已提交Read committed。
修改方法:
打开配置文件
修改隔离级别
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。