Django-数据库事务

在保存订单数据中,涉及到多张表(OrderInfo、OrderGoods、SKU)的数据修改,对这些数据的修改应该是一个整体事务,即要么一起成功,要么一起失败。

Django中对于数据库的事务,默认每执行一句数据库操作,便会自动提交。我们需要在保存订单中自己控制数据库事务的执行流程。

在Django中可以通过django.db.transaction模块提供的atomic来定义一个事务,atomic提供两种用法

 

装饰器用法:可以装饰在视图函数上

from django.db import transaction

@transaction.atomic
def viewfunc(request):
    # 这些代码会在一个事务中执行
    ...

 

with语句用法

from django.db import transaction

def viewfunc(request):
    # 这部分代码不在事务中,会被Django自动提交
    ...

    with transaction.atomic():
        # 这部分代码会在事务中执行
        ...

 

在Django中,还提供了保存点的支持,可以在事务中创建保存点来记录数据的特定状态,数据库出现错误时,可以恢复到数据保存点的状态

from django.db import transaction

# 创建保存点
save_id = transaction.savepoint()  

# 回滚到保存点
transaction.savepoint_rollback(save_id)

# 提交从保存点到当前状态的所有数据库事务操作
transaction.savepoint_commit(save_id)

 

并发处理

在多个用户同时发起对同一个商品的下单请求时,先查询商品库存,再修改商品库存,会出现资源竞争问题,导致库存的最终结果出现异常。

 

 

解决办法:

悲观锁

  • 当查询某条记录时,即让数据库为该记录加锁,锁住记录后别人无法操作,使用类似如下语法

select stock from tb_sku where id=1 for update;

SKU.objects.select_for_update().get(id=1)

悲观锁类似于我们在多线程资源竞争时添加的互斥锁,容易出现死锁现象,采用不多。

 

 

乐观锁

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

update tb_sku set stock=2 where id=1 and stock=7;

SKU.objects.filter(id=1, stock=7).update(stock=2)

 

任务队列

例如秒杀功能,将下单的逻辑放到任务队列中(如celery),将并行转为串行,所有人排队下单。比如开启只有一个进程的Celery,一个订单一个订单的处理。

使用乐观锁改写下单逻辑:

  1 def create(self, validated_data):
  2         """创建订单记录:保存OrderInfo和OrderGoods信息"""
  3 
  4         # 获取当前保存订单时需要的信息
  5         # 获取当前的登录用户
  6         user = self.context[‘request‘].user
  7         # 生成订单编号
  8         # order_id = ‘时间‘+‘user_id‘
  9         # timezone.now() == datetime类型的对象
 10         # 20180706085001+000000001
 11         order_id = timezone.now().strftime(‘%Y%m%d%H%M%S‘) + (‘%09d‘ % user.id)
 12         # 获取地址和支付方式
 13         address = validated_data.get(‘address‘)
 14         pay_method = validated_data.get(‘pay_method‘)
 15 
 16         # 明显的开启事务
 17         with transaction.atomic():
 18             # 在安全的地方,创建保存点,将来操作数据库失败回滚到此
 19             save_id = transaction.savepoint()
 20 
 21             try:
 22                 # 保存订单基本信息 OrderInfo
 23                 order = OrderInfo.objects.create(
 24                     order_id=order_id,
 25                     user = user,
 26                     address = address,
 27                     total_count = 0,
 28                     total_amount = 0,
 29                     freight = Decimal(‘10.00‘),
 30                     pay_method = pay_method,
 31                     # 如果用户传入的是"支付宝支付",那么下了订单后,订单的状态要是"待支付"
 32                     # 如果用户传入的是"货到付款",那么下了订单后,订单的状态要是"代发货"
 33                     status = OrderInfo.ORDER_STATUS_ENUM[‘UNPAID‘] if pay_method==OrderInfo.PAY_METHODS_ENUM[‘ALIPAY‘] else OrderInfo.ORDER_STATUS_ENUM[‘UNSEND‘]
 34                 )
 35 
 36                 # 从redis读取购物车中被勾选的商品信息
 37                 redis_conn = get_redis_connection(‘carts‘)
 38                 # 读取出所有的购物车数据
 39                 # redis_cart = {b‘1‘:b‘10‘, b‘2‘:b‘20‘, b‘3‘:b‘30‘}
 40                 redis_cart = redis_conn.hgetall(‘cart_%s‘ % user.id)
 41                 # cart_selected = [b‘1‘, b‘2‘]
 42                 cart_selected = redis_conn.smembers(‘selected_%s‘ % user.id)
 43                 # 定义将来要支付的商品信息的字典
 44                 # carts = {1:10, 2:20}
 45                 carts = {}
 46                 for sku_id in cart_selected:
 47                     carts[int(sku_id)] = int(redis_cart[sku_id])
 48 
 49                 # 读取出所有要支付的商品的sku_id
 50                 # sku_ids = [1,2]
 51                 sku_ids = carts.keys()
 52                 # 遍历购物车中被勾选的商品信息
 53                 for sku_id in sku_ids:
 54 
 55                     # 死循环的下单:当库存满足,你在下单时,库存没有同时的被别人的更改,下单成功
 56                     # 如果下单库存被更改看,但是你的sku_count依然在被更改后的库存范围内,继续下单
 57                     # 直到库存真的不满足条件时才下单失败
 58                     while True:
 59 
 60                         # 获取sku对象
 61                         sku = SKU.objects.get(id=sku_id)
 62 
 63                         # 获取原始的库存和销量
 64                         origin_stock = sku.stock
 65                         origin_sales = sku.sales
 66 
 67                         sku_count = carts[sku_id]
 68                         # 判断库存?
 69                         if sku_count > origin_stock:
 70                             # 回滚
 71                             transaction.savepoint_rollback(save_id)
 72                             raise serializers.ValidationError(‘库存不足‘)
 73 
 74                         # 模拟网络延迟
 75                         import time
 76                         time.sleep(5)
 77 
 78                         # 减少库存,增加销量 SKU?
 79                         # sku.stock -= sku_count
 80                             # origin_stock = sku.stock
 81                             # new_stock = origin_stock - sku_count
 82 
 83                         # sku.sales += sku_count
 84                         # sku.save()
 85 
 86                         # 读取要更新的库存和销量
 87                         new_stock = origin_stock - sku_count
 88                         new_sales = origin_sales + sku_count
 89 
 90                         # 使用乐观锁更新库存:在调用update()去更新库存之前,使用filter()拿着原始的库存去查询记录是否存在
 91                         # 如果记录不存在的,在调用update()时返回0
 92                         result = SKU.objects.filter(id=sku_id, stock=origin_stock).update(stock=new_stock, sales=new_sales)
 93                         if 0 == result:
 94                             # 死循环的下单:当库存满足,你在下单时,库存没有同时的被别人的更改,下单成功
 95                             # 如果下单库存被更改看,但是你的sku_count依然在被更改后的库存范围内,继续下单
 96                             # 直到库存真的不满足条件时才下单失败
 97                             continue
 98 
 99                         # 修改SPU销量
100                         sku.goods.sales += sku_count
101                         sku.goods.save()
102 
103                         # 保存订单商品信息 OrderGoods
104                         OrderGoods.objects.create(
105                             order=order,
106                             sku = sku,
107                             count = sku_count,
108                             price = sku.price,
109                         )
110 
111                         # 累加计算总数量和总价
112                         order.total_count += sku_count
113                         order.total_amount += (sku_count * sku.price)
114 
115                         # 下单成功要跳出死循环
116                         break
117 
118                 # 最后加入邮费和保存订单信息
119                 order.total_amount += order.freight
120                 order.save()
121             except serializers.ValidationError:
122                 # 这里不会滚的原因,是因为前面已经有了回滚的动作
123                 raise
124             except Exception:
125                 transaction.savepoint_rollback(save_id)
126                 raise # 自动的将捕获的异常抛出,不需要给异常起别名
127 
128             # 没有问题,需要明显的提交
129             transaction.savepoint_commit(save_id)
130 
131             # 清除购物车中已结算的商品
132             pl = redis_conn.pipeline()
133             pl.hdel(‘cart_%s‘ % user.id, *sku_ids)
134             pl.srem(‘selected_%s‘ % user.id, *sku_ids)
135             pl.execute()
136 
137             # 响应结果
138             return order
View Code

 

 

 

参考链接:

http://www.mamicode.com/info-detail-2383484.html

 

posted @ 2019-05-20 11:04  大西瓜Paul  阅读(441)  评论(0编辑  收藏  举报
/*增加返回顶部按钮*/ 返回顶部 /*给标题增加蓝色背景长条*/