问题分析1
提交订单时,涉及到多个表的操作,任何一个环节出了问题,就会造成数据不一致的问题,有可能出现订单已经生成,但商品数量未减少的情况。
在Django中可采用事务来保证一组操作要么全部成功,要么全部撤回(回滚)。
问题分析2:
当某商品数量为15,用户A与用户B几乎同时尝试购买这一商品,用户A买8个,用户B买10个,他们查询到的数量都为15,看似都能买到,但他们两个用户的总数为18,超过了库存15。
针对这个问题,可采用加锁的方式来保护资源。mysql提供两种锁的方式,悲观锁和乐观锁。悲观锁是在查询某条记录时对数据加锁,防止他人修改数据,这种处理方式会影响系统的并发量。乐观锁是在更新数据时通过条件判断来确定是否可以更新,如果条件正常,则更新;否则,表示资料被抢占,不再进行更新。
本项目采用乐观锁的方式。
实现
OrderCommitView视图实现了事务及乐观锁后的代码如下:
class OrderCommitView(LoginRequiredJSONMixin, View):
"""提交订单"""
def post(self, request):
# 接收参数
json_dict = json.loads(request.body.decode())
address_id = json_dict.get('address_id')
pay_method = json_dict.get('pay_method')
if not all([address_id, pay_method]):
return HttpResponseForbidden('缺少必要参数')
try:
address = Address.objects.get(id=address_id)
except Address.DoesNotExist:
return HttpResponseForbidden('参数address_id错误')
if pay_method not in [OrderInfo.PAY_METHODS_ENUM['CASH'], OrderInfo.PAY_METHODS_ENUM['ALIPAY']]:
return HttpResponseForbidden('参数pay_method错误')
# 获取用户
user = request.user
# 拼接订单id
order_id = timezone.localtime().strftime('%Y%m%d%H%M%S') + ('%09d' % user.id)
# 显式开启一个事务
with (transaction.atomic()):
try:
# 创建事务保存点
save_id = transaction.savepoint()
order = OrderInfo.objects.create(
order_id=order_id,
user=user,
address=address,
total_count=0,
total_amount=Decimal('0.00'),
freight=Decimal('10.00'),
pay_method=pay_method,
status=OrderInfo.ORDER_STATUS_ENUM['UNPAID'] if pay_method == OrderInfo.PAY_METHODS_ENUM[
'ALIPAY'] else
OrderInfo.ORDER_STATUS_ENUM['UNSEND'],
)
# 从redis中读取购物车中被勾选的商品 {b'1':b'1',b'2:b'2'}
redis_conn = get_redis_connection('carts')
redis_cart = redis_conn.hgetall('carts_%s' % user.id)
# 从redis数据库中选出被勾选的商品sku_id [b'1']
selected = redis_conn.smembers('selected_%s' % user.id)
carts = {}
for sku_id in selected:
carts[int(sku_id)] = int(redis_cart.get(sku_id))
# 通过sku模型类查询出在结算订单页面需要展示的商品sku
sku_ids = carts.keys()
# 遍历购物车中被勾选的商品
for sku_id in sku_ids:
while True: # ---------------
sku = SKU.objects.get(id=sku_id)
# 暂存原始库存销量
origin_stock = sku.stock # ---------------
origin_sales = sku.sales # ---------------
sku_count = carts[sku_id]
# 库存不足
if (sku_count > origin_stock):
# if (sku_count > sku.stock):
# 出错则回滚
transaction.savepoint_rollback(save_id)
return JsonResponse({'code': RETCODE.STOCKERR, 'errmsg': '库存不足'})
# 库存与销量更新
# sku.stock -= sku_count
# sku.sales += sku_count
# sku.save()
# 更新库存与销量
new_stock = origin_stock - sku_count
new_sales = origin_sales - sku_count
#
result = SKU.objects.filter(id=sku.id, stock=origin_stock).update(stock=new_stock, sales=new_sales)
if result == 0:
continue
# 修改spu销量
sku.spu.sales += sku_count
sku.spu.save()
# 保存订单商品信息
OrderGoods.objects.create(
order=order,
sku=sku,
count=sku_count,
price=sku.price,
)
order.total_count += sku_count
order.total_amount += sku.price * sku_count
break # ------------------
# 添加邮费
order.total_amount += order.freight
order.save()
except Exception as e:
logger.error(e)
transaction.savepoint_rollback(save_id)
return JsonResponse({'code': RETCODE.DBERR, 'errmsg': '下单失败'})
# 显式一次性提交事务
transaction.savepoint_commit(save_id)
# 清除购物车中已结算商品
pl = redis_conn.pipeline()
pl.hdel('carts_%s' % user.id, *selected)
pl.sadd('selected_%s' % user.id, *selected)
pl.execute()
return JsonResponse({'code': RETCODE.OK, 'errmsg': '下单成功', 'order_id': order.order_id})
修改mysql的配置
命令行下,mysql -u 用户名 -p 登录mysql数据库,然后执行下面的语句。
mysql> set global transaction isolation level READ COMMITTED;
mysql> SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
4645

被折叠的 条评论
为什么被折叠?



