2.4 使用乐观锁并发下单

重要提示:

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

2.4.1. 并发下单问题演示和解决方案

解决办法:

  • 悲观锁     【性能比较低,一般不考虑】

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

      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,一个订单一个订单的处理。

2.4.2. 使用乐观锁并发下单

思考:

  • 下单成功的条件是什么?

    • 首先库存大于购买量,然后更新库存和销量时原始库存没变。

结论:

  • 所以在用户库存满足的情况下,如果更新库存和销量时原始库存有变,那么继续给用户下单的机会。
class OrderCommitView(LoginRequiredJSONMixin, View):"""订单提交"""def post(self, request):"""保存订单信息和订单商品信息"""# 获取当前保存订单时需要的信息......# 显式的开启一个事务with transaction.atomic():# 创建事务保存点save_id = transaction.savepoint()# 暴力回滚try:# 保存订单基本信息 OrderInfo(一)order = OrderInfo.objects.create(order_id=order_id,user=user,address=address,total_count=0,total_amount=Decimal('0'),freight=Decimal('10.00'),pay_method=pay_method,status=OrderInfo.ORDER_STATUS_ENUM['UNPAID'] if pay_method == OrderInfo.PAY_METHODS_ENUM['ALIPAY'] elseOrderInfo.ORDER_STATUS_ENUM['UNSEND'])# 从redis读取购物车中被勾选的商品信息redis_conn = get_redis_connection('carts')redis_cart = redis_conn.hgetall('carts_%s' % user.id)selected = redis_conn.smembers('selected_%s' % user.id)carts = {}for sku_id in selected:carts[int(sku_id)] = int(redis_cart[sku_id])sku_ids = carts.keys()# 遍历购物车中被勾选的商品信息for sku_id in sku_ids:while True:# 查询SKU信息sku = SKU.objects.get(id=sku_id)# 读取原始库存origin_stock = sku.stockorigin_sales = sku.sales# 判断SKU库存sku_count = carts[sku.id]if sku_count > origin_stock:# 事务回滚transaction.savepoint_rollback(save_id)return http.JsonResponse({'code': RETCODE.STOCKERR, 'errmsg': '库存不足'})# 模拟延迟# import time# time.sleep(5)# SKU减少库存,增加销量# sku.stock -= sku_count# sku.sales += sku_count# sku.save()# 乐观锁更新库存和销量new_stock = origin_stock - sku_countnew_sales = origin_sales + sku_countresult = 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_countsku.spu.save()# 保存订单商品信息 OrderGoods(多)OrderGoods.objects.create(order=order,sku=sku,count=sku_count,price=sku.price,)# 保存商品订单中总价和总数量order.total_count += sku_countorder.total_amount += (sku_count * sku.price)# 下单成功或者失败就跳出循环break# 添加邮费和保存订单信息order.total_amount += order.freightorder.save()except Exception as e:logger.error(e)# 事务回滚transaction.savepoint_rollback(save_id)return http.JsonResponse({'code': RETCODE.DBERR, 'errmsg': '下单失败'})# 保存订单数据成功,显式的提交一次事务transaction.savepoint_commit(save_id)# 清除购物车中已结算的商品pl = redis_conn.pipeline()pl.hdel('carts_%s' % user.id, *selected)pl.srem('selected_%s' % user.id, *selected)pl.execute()# 响应提交订单结果return http.JsonResponse({'code': RETCODE.OK, 'errmsg': '下单成功', 'order_id': order.order_id})

2.4.3. MySQL事务隔离级别

  • 事务隔离级别指的是在处理同一个数据的多个事务中,一个事务修改数据后,其他事务何时能看到修改后的结果。

  • MySQL数据库事务隔离级别主要有四种:

    • Serializable:串行化,一个事务一个事务的执行。   【并发性比较低】
    • Repeatable read:可重复读,无论其他事务是否修改并提交了数据,在这个事务中看到的数据值始终不受其他事务影响。
    • Read committed:读取已提交,其他事务提交了对数据的修改后,本事务就能读取到修改后的数据值。
    • Read uncommitted:读取未提交,其他事务只要修改了数据,即使未提交,本事务也能看到修改后的数据值。
    • MySQL数据库默认使用可重复读( Repeatable read)。
  • 使用乐观锁的时候,如果一个事务修改了库存并提交了事务,那其他的事务应该可以读取到修改后的数据值,所以不能使用可重复读的隔离级别,应该修改为读取已提交(Read committed)。

  • 修改方式:  【若永久有效,直接修改下面的配置】

2.5 展示提交订单成功页面

支付方式:货到付款

支付方式:支付宝

1.请求方式

选项 方案
请求方法 GET
请求地址 /orders/success/
#  提交订单成功url(r'^orders/success/$', views.OrderSuccessView.as_view()),

2.请求参数:

3.响应结果:HTML

order_success.html

4.后端接口定义和实现

class OrderSuccessView(LoginRequiredMixin, View):"""提交订单成功"""def get(self, request):order_id = request.GET.get('order_id')payment_amount = request.GET.get('payment_amount')pay_method = request.GET.get('pay_method')context = {'order_id':order_id,'payment_amount':payment_amount,'pay_method':pay_method}return render(request, 'order_success.html', context)

5.渲染提交订单成功页面信息

<div class="common_list_con clearfix"><div class="order_success"><p><b>订单提交成功,订单总价<em>¥{{ payment_amount }}</em></b></p><p>您的订单已成功生成,选择您想要的支付方式,订单号:{{ order_id }}</p><p><a href="{{ url('orders:info', args=(1, )) }}">您可以在【用户中心】->【我的订单】查看该订单</a></p></div>
</div>
<div class="order_submit clearfix">{% if pay_method == '1' %}<a href="{{ url('contents:index') }}">继续购物</a>{% else %}<a @click="order_payment" class="payment">去支付</a>{% endif %}
</div>

美多商城之订单(提交订单2)相关推荐

  1. 美多商城之支付(评价订单商品)

    评价订单商品 提示: 点击<我的订单>页面中的<待评价>按钮,进入到订单商品评价页面. 一.评价订单商品 1. 展示商品评价页面 1.请求方式 选项 方案 请求方法 GET 请 ...

  2. Vue3电商项目实战-结算支付 3【05-结算-收货地址-添加、06-结算-收货地址-修改、07-结算-提交订单】

    文章目录 05-结算-收货地址-添加 06-结算-收货地址-修改 07-结算-提交订单 05-结算-收货地址-添加 目的:实现收货地址的添加. 大致步骤: 独立组件,准备一个对话框 完成表单布局 完成 ...

  3. 美多商城项目订单和支付模块总结

    订单完成 订单结算页面 订单展示用的序列化器 # 前端需要运费数据和商品信息数据的字典列表,这里使用嵌套序列化器返回数据 class CartSKUSerializer(serializers.Mod ...

  4. 移动商城第十九篇【提交订单】

    tags: 移动商城项目 订单提交预备 当结算完之后会跳转到订单提交页面.首先我们来看一下订单提交页面是怎么样的: 我们需要把用户的地址.商品的信息查询出来在页面上展示! 那么在跳转到确认页面的时候, ...

  5. 模拟商城的购物过程Java_编写Java程序,模拟网上商城购物,当用户选好物品提交订单时,每笔订单会自动生成一个唯一的订单编号。...

    需求说明: 模拟网上商城购物,当用户选好物品提交订单时,每笔订单会自动生成一个唯一的订单编号.而部分电子商务网站在数据高峰期时,一毫秒可能需要处理近千笔的订单 现在简单模拟 5 个订单同时提交,生成唯 ...

  6. 美多商城之订单(结算订单)

    订单 提示: 订单入口 在<购物车>页面的<去结算>. <去结算>后进入到<结算订单>页面,展示出要结算的商品信息. 一.结算订单 1.1.1. 结算订 ...

  7. Java Web 网络商城案例演示十五 订单详情功能(提交订单支付界面)

    订单详情功能(提交订单支付界面) 原理分析 步骤实现: 1.准备工作:order_list.jsp当中修改链接 提交当前订单编号 <a href="${pageContext.requ ...

  8. 谷粒商城项目篇13_分布式高级篇_订单业务模块(提交订单幂等性、分布式事务、延时MQ实现定时任务)

    目录 一.订单业务模块 订单流程 购物车跳转订单确认页 登录拦截器 封装vo Feign远程调用丢失请求头信息 Feign远程异步调用丢失上下文信息 提交订单接口幂等性 令牌token机制 各种锁机制 ...

  9. 移动商城第七篇【购物车增删改查、提交订单】

    把商品加入购物车 接下来我们要做的就是将商品加入到购物车中.我们这次使用的是Cookie来将用户的信息存储起来.那为什么要用cookie呢?? 那我们现在决定将购物车存储在Cookie中了,那Cook ...

最新文章

  1. 在C语言中解析json配置文件
  2. 使用arthas采集火焰图
  3. 全新WiFi技术问世 更适合智能家庭和物联网
  4. 943c语言,考研备战:华南理工大学943计算方法(含C语言)复试大纲_跨考网
  5. mysql批量insert数据锁表_批量插入数据产生锁阻塞的问题
  6. Linux系列开坑记(二)-神的编辑器Vim
  7. Gitee ssh 公钥配置好后,仍然 permission denied 的排查过程及解决方法
  8. 贝叶斯分类器_python机器学习API介绍10:多项式贝叶斯分类器
  9. MQ消息队列之MSMQ
  10. qt 复制字符串_Qt字符串处理 QString用法总结(一)
  11. vs2017编写模板类出现以下错误时:没有与参数列表匹配的构造函数……
  12. golang protobuf 动态消息获取_干货|Golang拦截器的一种实现
  13. 阶段5 3.微服务项目【学成在线】_day01 搭建环境 CMS服务端开发_11-MongoDb入门-安装Mongodb数据库...
  14. generator config_springboot集成mybatis+Generator代码生成
  15. 时间序列--平稳性介绍及检验方法
  16. 计算机网络高校校园网设计思路,网络工程设计与实现程设计高校校园网设计方案.doc...
  17. 河北农业大学林学可转计算机系吗,河北农业大学专业排名,招生专业目录(10篇)...
  18. 软考高级系统架构设计师:五大类安全服务
  19. wps怎么画网络图_wps 流程图怎么画 WPS流程图绘制图解教程
  20. winform遍历bartender_标签打印软件 - 第25页 共52页 - BarTender

热门文章

  1. “奥利”来啦,腾讯Robotics X实验室跑出的“轮滑小子”
  2. 高手的习惯:pythonic风格代码
  3. 全领域通吃,12个经典Python数据可视化库盘点
  4. 掌握深度学习,为什么要用PyTorch、TensorFlow框架?
  5. 倒计时1天 | 专属技术人的盛会,为你而来!
  6. 崛起的Python,真的影响了76万人?
  7. 普元王葱权:数字化时代需要新一代的大数据应用平台架构
  8. 雷军深情告白:在我心里,武汉大学是全球最好的大学
  9. 干货 | 如何使用 CNN 推理机在 IoT 设备上实现深度学习
  10. Spring Boot 操作 Redis 的各种实现