订单并发这个问题我想你们都是有必定认识的,这里我说一下个人一些浅见,我会尽量的让你们了解如何解决这类问题。python
在解释如何解决订单并发问题以前,须要先了解一下什么是数据库的事务。(我用的是mysql数据库,这里以mysql为例)mysql
一组mysql语句,要么执行,要么全不不执行。redis
Read Committed(读取提交内容)sql
若是是Django2.0如下的版本,须要去修改到这个隔离级别,否则乐观锁操做时没法读取已经被修改的数据数据库
RepeatableRead(可重读)多线程
这是这是Mysql默认的隔离级别,能够到mysql的配置文件中去修改;并发
transcation-isolation = READ-COMMITTED
复制代码
在mysql配置文件中添加这行而后重启mysql就能够将事务隔离级别修改至Read Committedpost
其余事务知识这里不会用到就不浪费时间去作介绍了。学习
悲观锁:开启事务,而后给mysql的查询语句最后加上for update。atom
这是在干什么呢。可能你们有些不理解,其实就是给资源加上和多线程中加互斥锁同样的东西,确保在一个事务结束以前,别的事务没法对该数据进行操做。
下面是悲观锁的代码,加锁和解锁都是须要消耗CPU资源的,因此在订单并发少的状况使用乐观锁会是一个更好的选择。
''' 遇到问题没人解答?小编建立了一个Python学习交流QQ群:857662006 寻找有志同道合的小伙伴, 互帮互助,群里还有不错的视频学习教程和PDF电子书! '''
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':'订单建立成功'})
复制代码