Django电商项目(八)订单生成、悲观锁、乐观锁
Django电商项目
- 订单生成
- mysql事务
- django使用事务
- 提交订单页面
- 创建订单后台view
订单生成
mysql事务
事务概念
一组mysql语句,要么执行,要么全不不执行。事务的特点
1、原子性:一组事务,要么成功;要么撤回。
2、稳定性 :有非法数据(外键约束之类),事务撤回。
3、隔离性:事务独立运行。一个事务处理后的结果,影响了其他事务,那么其他事务会撤回。事务的100%隔离,需要牺牲速度。
4、可靠性:软、硬件崩溃后,InnoDB数据表驱动会利用日志文件重构修改。可靠性和高速度不可兼得, innodb_flush_log_at_trx_commit 选项 决定什么时候吧事务保存到日志里。事务控制语句
BEGIN或START TRANSACTION;显式地开启一个事务;
COMMIT;也可以使用COMMIT WORK,不过二者是等价的。COMMIT会提交事务,并使已对数据库进行的所有修改称为永久性的;
ROLLBACK;有可以使用ROLLBACK WORK,不过二者是等价的。回滚会结束用户的务,并撤销正在进行的所有未提交的修改;
SAVEPOINT identifier;SAVEPOINT允许在事务中创建一个保存点,一个事务中可以有多个SAVEPOINT;
RELEASE SAVEPOINT identifier;删除一个事务的保存点,当没有指定的保存点时,执行该语句会抛出一个异常;
ROLLBACK TO identifier;把事务回滚到标记点;mysql事务隔离级别
SQL标准定义了4类隔离级别,包括了一些具体规则,用来限定事务内外的哪些改变是可见的,哪些是不可见的。低级别的隔离级一般支持更高的并发处理,并拥有更低的系统开销。
Read Uncommitted(读取未提交内容)
在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。读取未提交的数据,也被称之为脏读(Dirty Read)。
Read Committed(读取提交内容)
这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这种隔离级别 也支持所谓的不可重复读(Nonrepeatable Read),因为同一事务的其他实例在该实例处理其间可能会有新的commit,所以同一select可能返回不同结果。
Repeatable Read(可重读)
这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上,这会导致另一个棘手的问题:幻读 (Phantom Read)。简单的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行。InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了该问题。
==Serializable(可串行化) ==
这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。
这四种隔离级别采取不同的锁类型来实现,若读取的是同一个数据的话,就容易发生问题。例如:
脏读(Drity Read):某个事务已更新一份数据,另一个事务在此时读取了同一份数据,由于某些原因,前一个RollBack了操作,则后一个事务所读取的数据就会是不正确的。
不可重复读(Non-repeatable read):在一个事务的两次查询之中数据不一致,这可能是两次查询过程中间插入了一个事务更新的原有的数据。
幻读(Phantom Read):在一个事务的两次查询中数据笔数不一致,例如有一个事务查询了几行(Row)数据,而另一个事务却在此时插入了新的几行数据,先前的事务在接下来的查询中,就会发现有几行数据是它先前所没有的。
设置mysql事务的隔离级别
打开mysql配置文件: sudo vi /etc/mysql/mysql.conf.d/mysqld.cnf, 添加如下行。
保存配置文件,重启mysql服务。
sudo service mysql restart
django使用事务
参考文档:https://doc.codingdict.com/django/topics/db/transactions.html
导包
from django.db import transaction
使用装饰器
@transaction.atomic
提交订单页面
修改cart模板
创建函数
# /order/place
class OrderPlaceView(LoginRequiredMixin, View):'''提交订单页面显示'''def post(self, request):'''提交订单页面显示'''# 获取登录的用户user = request.user# 获取参数sku_idssku_ids = request.POST.getlist('sku_ids') # [1,26]# 校验参数if not sku_ids:# 跳转到购物车页面return redirect(reverse('cart:show'))conn = get_redis_connection('default')cart_key = 'cart_%d'%user.idskus = []# 保存商品的总件数和总价格total_count = 0total_price = 0# 遍历sku_ids获取用户要购买的商品的信息for sku_id in sku_ids:# 根据商品的id获取商品的信息sku = GoodsSKU.objects.get(id=sku_id)# 获取用户所要购买的商品的数量count = conn.hget(cart_key, sku_id)# 计算商品的小计amount = sku.price*int(count)# 动态给sku增加属性count,保存购买商品的数量sku.count = count# 动态给sku增加属性amount,保存购买商品的小计sku.amount = amount# 追加skus.append(sku)# 累加计算商品的总件数和总价格total_count += int(count)total_price += amount# 运费:实际开发的时候,属于一个子系统transit_price = 10 # 写死# 实付款total_pay = total_price + transit_price# 获取用户的收件地址addrs = Address.objects.filter(user=user)# 组织上下文sku_ids = ','.join(sku_ids) # [1,25]->1,25context = {'skus':skus,'total_count':total_count,'total_price':total_price,'transit_price':transit_price,'total_pay':total_pay,'addrs':addrs,'sku_ids':sku_ids}# 使用模板return render(request, 'place_order.html', context)
修改place_order模板 创建订单前端js
{% extends 'base_no_cart.html' %}
{% load staticfiles %}
{% block title %}天天生鲜-提交订单{% endblock title %}
{% block page_title %}提交订单{% endblock page_title %}
{% block body %}<h3 class="common_title">确认收货地址</h3><div class="common_list_con clearfix"><dl><dt>寄送到:</dt>{% for addr in addrs %}<dd><input type="radio" name="addr_id" value="{{ addr.id }}" {% if addr.is_default %}checked{% endif %}>{{ addr.addr }} ({{ addr.receiver }} 收) {{ addr.phone }}</dd>{% endfor %}</dl><a href="{% url 'user:address' %}" class="edit_site">编辑收货地址</a></div><h3 class="common_title">支付方式</h3> <div class="common_list_con clearfix"><div class="pay_style_con clearfix"><input type="radio" name="pay_style" value="1" checked><label class="cash">货到付款</label><input type="radio" name="pay_style" value="2"><label class="weixin">微信支付</label><input type="radio" name="pay_style" value="3"><label class="zhifubao"></label><input type="radio" name="pay_style" value="4"><label class="bank">银行卡支付</label></div></div><h3 class="common_title">商品列表</h3><div class="common_list_con clearfix"><ul class="goods_list_th clearfix"><li class="col01">商品名称</li><li class="col02">商品单位</li><li class="col03">商品价格</li><li class="col04">数量</li><li class="col05">小计</li> </ul>{% for sku in skus %}<ul class="goods_list_td clearfix"><li class="col01">{{ forloop.counter }}</li><li class="col02"><img src="{{ sku.image.url }}"></li><li class="col03">{{ sku.name }}</li><li class="col04">{{ sku.unite }}</li><li class="col05">{{ sku.price }}元</li><li class="col06">{{ sku.count }}</li><li class="col07">{{ sku.amount }}元</li></ul>{% endfor %}</div><h3 class="common_title">总金额结算</h3><div class="common_list_con clearfix"><div class="settle_con"><div class="total_goods_count">共<em>{{ total_count }}</em>件商品,总金额<b>{{ total_price }}元</b></div><div class="transit">运费:<b>{{ transit_price }}元</b></div><div class="total_pay">实付款:<b>{{ total_pay }}元</b></div></div></div><div class="order_submit clearfix">{% csrf_token %}<a href="javascript:;" sku_ids={{ sku_ids }} id="order_btn">提交订单</a></div>
{% endblock body %}
{% block bottom %}<div class="popup_con"><div class="popup"><p>订单提交成功!</p></div><div class="mask"></div></div>
{% endblock bottom %}
{% block bottomfiles %}<script type="text/javascript" src="{% static 'js/jquery-1.12.4.min.js' %}"></script><script type="text/javascript">$('#order_btn').click(function() {// 获取用户选择的地址id, 支付方式, 要购买的商品id字符串addr_id = $('input[name="addr_id"]:checked').val()pay_method = $('input[name="pay_style"]:checked').val()sku_ids = $(this).attr('sku_ids')csrf = $('input[name="csrfmiddlewaretoken"]').val()// alert(addr_id+":"+pay_method+':'+sku_ids)// 组织参数params = {'addr_id':addr_id, 'pay_method':pay_method, 'sku_ids':sku_ids,'csrfmiddlewaretoken':csrf}// 发起ajax post请求,访问/order/commit, 传递的参数: addr_id pay_method, sku_ids$.post('/order/commit', params, function (data) {if (data.res == 5){// 创建成功localStorage.setItem('order_finish',2);$('.popup_con').fadeIn('fast', function() {setTimeout(function(){$('.popup_con').fadeOut('fast',function(){window.location.href = '/user/order/1';});},3000)});}else{alert(data.errmsg)}})});</script>
{% endblock bottomfiles %}
创建订单后台view
# 前端传递的参数:地址id(addr_id) 支付方式(pay_method) 用户要购买的商品id字符串(sku_ids)
# mysql事务: 一组sql操作,要么都成功,要么都失败
# 高并发:秒杀
# 支付宝支付
class OrderCommitView(View):'''订单创建'''@transaction.atomicdef post(self, request):'''订单创建'''# 判断用户是否登录user = request.userif not user.is_authenticated():# 用户未登录return JsonResponse({'res':0, 'errmsg':'用户未登录'})# 接收参数addr_id = request.POST.get('addr_id')pay_method = request.POST.get('pay_method')sku_ids = request.POST.get('sku_ids') # 1,3# 校验参数if not all([addr_id, pay_method, sku_ids]):return JsonResponse({'res':1, 'errmsg':'参数不完整'})# 校验支付方式if pay_method not in OrderInfo.PAY_METHODS.keys():return JsonResponse({'res':2, 'errmsg':'非法的支付方式'})# 校验地址try:addr = Address.objects.get(id=addr_id)except Address.DoesNotExist:# 地址不存在return JsonResponse({'res':3, 'errmsg':'地址非法'})# todo: 创建订单核心业务# 组织参数# 订单id: 20171122181630+用户idorder_id = datetime.now().strftime('%Y%m%d%H%M%S')+str(user.id)# 运费transit_price = 10# 总数目和总金额total_count = 0total_price = 0# 设置事务保存点save_id = transaction.savepoint()try:# todo: 向df_order_info表中添加一条记录order = OrderInfo.objects.create(order_id=order_id,user=user,addr=addr,pay_method=pay_method,total_count=total_count,total_price=total_price,transit_price=transit_price)# todo: 用户的订单中有几个商品,需要向df_order_goods表中加入几条记录conn = get_redis_connection('default')cart_key = 'cart_%d'%user.idsku_ids = sku_ids.split(',')for sku_id in sku_ids:for i in range(3):# 获取商品的信息try:sku = GoodsSKU.objects.get(id=sku_id)except:# 商品不存在transaction.savepoint_rollback(save_id)return JsonResponse({'res':4, 'errmsg':'商品不存在'})# 从redis中获取用户所要购买的商品的数量count = conn.hget(cart_key, sku_id)# todo: 判断商品的库存if int(count) > sku.stock:transaction.savepoint_rollback(save_id)return JsonResponse({'res':6, 'errmsg':'商品库存不足'})# todo: 更新商品的库存和销量orgin_stock = sku.stocknew_stock = orgin_stock - int(count)new_sales = sku.sales + int(count)# print('user:%d times:%d stock:%d' % (user.id, i, sku.stock))# import time# time.sleep(10)# update df_goods_sku set stock=new_stock, sales=new_sales# where id=sku_id and stock = orgin_stock# 返回受影响的行数res = GoodsSKU.objects.filter(id=sku_id, stock=orgin_stock).update(stock=new_stock, sales=new_sales)if res == 0:if i == 2:# 尝试的第3次transaction.savepoint_rollback(save_id)return JsonResponse({'res': 7, 'errmsg': '下单失败2'})continue# todo: 向df_order_goods表中添加一条记录OrderGoods.objects.create(order=order,sku=sku,count=count,price=sku.price)# todo: 累加计算订单商品的总数量和总价格amount = sku.price*int(count)total_count += int(count)total_price += amount# 跳出循环break# todo: 更新订单信息表中的商品的总数量和总价格order.total_count = total_countorder.total_price = total_priceorder.save()except Exception as e:transaction.savepoint_rollback(save_id)return JsonResponse({'res':7, 'errmsg':'下单失败'})# 提交事务transaction.savepoint_commit(save_id)# todo: 清除用户购物车中对应的记录conn.hdel(cart_key, *sku_ids)# 返回应答return JsonResponse({'res':5, 'message':'创建成功'})
(1) 悲观锁
select * from df_goods_sku where id=17 for update;
悲观锁获取数据时对数据行了锁定,其他事务要想获取锁,必须等原事务结束。
(2) 乐观锁
查询时不锁数据,提交更改时进行判断.
update df_goods_sku set stock=0, sales=1 where id=17 and stock=1;
冲突比较少的时候,使用乐观锁。
冲突比较多的时候,使用悲观锁。
Django电商项目(八)订单生成、悲观锁、乐观锁相关推荐
- Django电商项目—项目规划和环境搭建
Django商品 我们要做一个Django电商项目,那么在开始我们需要将项目分析,功能块,还有逻辑弄清楚, 1.功能块 版本 优化思想 商城项目: 买家:购物车 卖家:店铺 商品 我们首先要先把他们的 ...
- Django电商项目(六)商品详情页、列表页分页、商品搜索
Django电商项目 商品详情页 商品列表页 商品搜索 全文检索 安装和配置 索引文件生成 全文检索的使用 改变分词方式 商品详情页 新建detail.html {% extends 'base_de ...
- 电商项目实战之缓存与Redis分布式锁
电商项目实战之缓存与Redis分布式锁 缓存失效 缓存穿透 缓存雪崩 缓存击穿 分布式缓存 分布式锁 SpringBoot整合Redisson实现分布式锁 实现过程 缓存和数据库一致性 场景分析 解决 ...
- 电商项目中订单系统到底该怎么设计才好?(至尊典藏版)
目录 前言 一.订单系统架构 1. 订单系统在企业中的角色 2. 订单系统与各业务系统的关系 3. 订单系统上下游关系编辑 4. 订单系统的业务架构编辑 二.订单系统核心功能 1. 订单中所包含的 ...
- 【电商】后台订单生成
结合商品流转的电商系列介绍了一些了,商品已经采购入库.价格税率设置好了.活动及相关模板也已经准备完毕,下面就应该上架销售了,现在接着聊下订单的生成. 订单从产生到最终的关闭需要经历很多的环节,订单也是 ...
- [Vue.js]实战 -- 电商项目(八)
数据统计 数据统计概述 用于统计电商平台运营过程的中的各种统计数据 通过直观的可视化方式展示出来,方便相关运营和管理人员查看 用户来源数据统计报表 Echarts 第三方可视化库的基本使用 安装ech ...
- Django电商项目(八)短信验证码的前后端实现
这篇文章我们开始完成短信验证码的前后端逻辑 后端逻辑 短信验证码接口设计 class SMSCodeView(View):"""短信验证码""&quo ...
- Django电商项目(十二)实现QQ登录
QQ登录:即我们所说的第三方登录,是指用户可以不在本项目中输入密码,而直接通过第三方的验证,成功登录本项目. QQ互联开发者申请步骤 若想实现QQ登录,需要成为QQ互联的开发者,审核通过才可实现. 相 ...
- java电商项目搭建-------订单模块
努力好了,时间会给你答案.--------magic_guo 订单模块的流程以及用户操作如下: 1.当用户选定购物车需要结算的商品并单击结算按钮时,会跳转到订单页面,此时会将选定的购物车信息展示在订单 ...
最新文章
- 打开AI芯片的“万能钥匙”
- python 字符串补齐
- jquery取值,赋值,以及下拉框获取选中value值
- mysql infile local,MySQL:启用LOAD DATA LOCAL INFILE
- RocketMQ源码解析-事务消息的二阶段提交
- oracle rac redo log,RAC共享online redo log和archived log的官方说明
- [转]用 ASP.NET 2.0 改进的 ViewState 加快网站速度
- sdk manager的列表怎么消失了_腾讯安全SDK的Dll Dump研究
- u盘文件夹变成应用程序怎么恢复?方法来了!
- 计算机中常用的数学知识
- eclipse导入系统签名
- PS抠头发妙法(原创技巧)
- Linux系统如何替换新硬盘
- QT入门学习之软件程序开发初体验
- matlab读取excel数据
- 全国计算机一共几集,夏目友人帐第六季一共多少集?至少11集国内已播出
- 数据分析方法和思维—RFM用户分群
- 如何在mysql官网下载mysql最新或者以前的版本
- 在网页中插入一个透明背景的PNG图片
- CPU虚拟化的三种技术
热门文章
- python xlwt表格写入操作
- ssd硬盘 速度慢 linux,固态硬盘速度太慢 快不起来?你或许犯了这四个SSD使用错误 (全文)...
- 建筑施工与管理计算机综合应用能力实训报告,建筑施工管理计算机综合应用能力实训报告...
- 将一个div中的两个P元素同一行居中显示
- 盘点:当今十大备份应用软件(转)
- 彗星http_大气与彗星
- 围剿慢SQL,工行MySQL研发管控和治理实践
- 工具-python包-虚拟环境管理(99.4.1)
- jmeter压力测试并发
- matlab示波器绘图工具,快速绘制Simulink示波器波形