Django电商项目(八)订单生成、悲观锁、乐观锁

订单生成

在这里插入图片描述

mysql事务

  1. 事务概念
    一组mysql语句,要么执行,要么全不不执行。

  2. 事务的特点
    1、原子性:一组事务,要么成功;要么撤回。
    2、稳定性 :有非法数据(外键约束之类),事务撤回。
    3、隔离性:事务独立运行。一个事务处理后的结果,影响了其他事务,那么其他事务会撤回。事务的100%隔离,需要牺牲速度。
    4、可靠性:软、硬件崩溃后,InnoDB数据表驱动会利用日志文件重构修改。可靠性和高速度不可兼得, innodb_flush_log_at_trx_commit 选项 决定什么时候吧事务保存到日志里。

  3. 事务控制语句
    BEGIN或START TRANSACTION;显式地开启一个事务;
    COMMIT;也可以使用COMMIT WORK,不过二者是等价的。COMMIT会提交事务,并使已对数据库进行的所有修改称为永久性的;
    ROLLBACK;有可以使用ROLLBACK WORK,不过二者是等价的。回滚会结束用户的务,并撤销正在进行的所有未提交的修改;
    SAVEPOINT identifier;SAVEPOINT允许在事务中创建一个保存点,一个事务中可以有多个SAVEPOINT;
    RELEASE SAVEPOINT identifier;删除一个事务的保存点,当没有指定的保存点时,执行该语句会抛出一个异常;
    ROLLBACK TO identifier;把事务回滚到标记点;

  4. 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)数据,而另一个事务却在此时插入了新的几行数据,先前的事务在接下来的查询中,就会发现有几行数据是它先前所没有的。
    在这里插入图片描述

  5. 设置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 %}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.atomic
    def post(self, request):
        '''订单创建'''
        # 判断用户是否登录
        user = request.user
        if 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+用户id
        order_id = datetime.now().strftime('%Y%m%d%H%M%S')+str(user.id)

        # 运费
        transit_price = 10

        # 总数目和总金额
        total_count = 0
        total_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.id

            sku_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.stock
                    new_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_count
            order.total_price = total_price
            order.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;
冲突比较少的时候,使用乐观锁。
冲突比较多的时候,使用悲观锁。

猜你喜欢

转载自blog.csdn.net/qq_27251475/article/details/120824888