Django电商项目
订单生成
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_ids
sku_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.id
skus = []
# 保存商品的总件数和总价格
total_count = 0
total_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,25
context = {
'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