赞
踩
在多个用户同时发起对同一个数据提交修改操作时(先查询,再修改),会出现资源竞争的问题,导致最终修改的数据结果出现异常。
比如限量商品在热销时,当多个用户同时请求购买商品时,最终修改的数据就会出现异常
下面我们来写点代码还原一下现象:
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视图类,
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)
4.定义路由:
from django.conf.urls import url
from . import views
urlpatterns =[
url(r'^goods/$', views.Goods.as_view()),
]
我们来使用两个postman模拟A,B用户同时请求,用户A买6套商品,用户B买5套商品
运行结果:
分析及结论:
当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
如果使用给数据库加锁的方式,在给处理多个商品时可能会出现死锁,所以使用数据库中的乐观锁方式来处理效果较好
数据库乐观锁:
乐观锁并不是真实存在的锁,而是在更新的时候判断此时的库存是否是之前查询出的库存,如果相同,表示没人修改,可以更新库存,否则表示别人抢过资源,不再执行库存更新。类似如下操作
使用原生的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)
多用户请求结果:
事务隔离级别指的是在处理同一个数据的多个事务中,一个事务修改数据后,其他事务何时能看到修改后的结果。
MySQL数据库事务隔离级别主要有四种:
Serializable 串行化,一个事务一个事务的执行
Repeatable read 可重复读,无论其他事务是否修改并提交了数据,在这个事务中看到的数据值始终不受其他事务影响
Read committed 读取已提交,其他事务提交了对数据的修改后,本事务就能读取到修改后的数据值
Read uncommitted 读取为提交,其他事务只要修改了数据,即使未提交,本事务也能看到修改后的数据值。
MySQL数据库默认使用可重复读( Repeatable read),而使用乐观锁的时候,如果一个事务修改了库存并提交了事务,那其他的事务应该可以读取到修改后的数据值,所以不能使用可重复读的隔离级别,应该修改为读取已提交Read committed。
修改方法:
打开配置文件
修改隔离级别
OK
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。