事务:介绍及使用

(1)数据库事务

==关系数据库中的一系列操作的集合,该集合中的操作要么全部执行,要么全部不执行。

== 它具有ACID四大特性:

==原子性==(Atomicity);指事务是一个不可分割的单元,事务中的操作要么全部执行,要么全部不执行。

==一致性==(Consistency);事务执行前后数据的完整性必须保持一致。这里的完整性包括实体完整性、参照完整性、用户定义的完整性。

==隔离性==(Isolation);多个用户并发访问数据库时,数据库为每一个用户开启一个事务,多个事务并发执行时,相互之间是隔离开的,互不影响。

==持久性==(Durability);事务一旦被提交,对数据库中数据的改变是永久性性的。  

(2)事务的隔离级别

==读未提交==;
事务A更新了数据,但是还没提交;事务B就读取到A更新的数据,事务A回滚后,B就看不到更新的数据。这种现象就是读未提交,即==脏读==

==读已提交==;

事务A更新数据并完成提交;事务B内部就可以读取到A更新的数据。

如:开始时,事务A、B读取的库存量都是100; 事务A更新库存为80,并进行了提交。由于事务B处理比较耗时,还没执行结束,B再次读取库存时,已经变成了80;最后B在更新库存时就要在80的基础上进行更新。

==重复读==;

事务A更新数据并完成提交;在事务B提交之前,其内部无法读取到A更新的数据,只能读取到之前的旧数据。(普通查询,即快照读) 容易出现==幻读==: 对于innodb存储引擎,可重复读的隔离级别,使用当前读(加共享锁)时容易出现幻读,即一个事务进行两次相同条件的查询时,后一次查询看到了上一次没有看到的行。

# 普通查询(快照读),可以重复读
mysql> select * from stu;

# 当前读(加共享锁),容易出现幻读,针对读取到的新行
mysql> select * from stu lock in share mode;
# 加入共享锁后,其他的事务无法进行写入操作,可以读

# 提交/回滚事务,才释放锁
mysql> commit/rollback  

==串行化==;

所有事务串行执行,一个执行完成,再执行另一个。

(3)事务的shell测试

#查询当前会话(终端)的事务隔离级别(Mysql 8.0 +)
select @@transaction_isolation; # 查看当前事务的隔离级别
select @@global.transaction_isolation; # 查看当前系统的事务隔离级别

#设置当前会话的事务隔离级别
set session transaction isolation level read uncommitted;
set session transaction isolation level read committed;
set session transaction isolation level repeatable read;
set session transaction isolation level serializable 

(4)锁概念

==悲观锁==:在查询语句上加锁,保证多事务==写入时==串行执行。 例如,在多个用户同时进行订单提交时,哪个事务先执行到该语句则获取锁,等事务结束后才会释放,其他事务阻塞等待获取锁,保证了同一时刻只有一个事务在写入,虽然保证了数据的一致性,但是牺牲了系统的处理并发的能力;

==乐观锁==:查询时不加锁,在变更时对比原数据与当前重新查询的数据是否一致,若不一致则本次变更失败(即乐观的认为当前没有其他事务在同时进行此过程),一般采用3次循环重复此过程,由于mysql数据库事务的隔离性默认为重复读,在事务结束之前,查询到的原数据不会更新,因此需要修改数据库事务隔离的级别为read committed(在Django2.0中已经自动将所有数据库的事务隔离级别修改为read committed),此时乐观锁可执行。

在冲突较少时,如订单并发量较少,使用乐观锁(省去加锁、释放锁的开销,提高性能),在冲突较多时,如订单并发量大,使用悲观锁。一般将整个事务的过程都放置于try中。

乐观锁和悲观锁:

#查询语句不加锁,即乐观锁
good = Goods.objects.get(id=good_id)

#查询语句加锁,即悲观锁,冲突较多时使用,且只能在事务内部使用
from django.db import transaction
with transaction.atomic():
	point = transaction.savepoint()
	try:
		good = Goods.objects.select_for_update().get(id=good_id) #查询语句加锁,其他事务无法写入
	except:
		transaction.savepoint_rollback(point)
	transaction.savepoint_commit(point)

悲观锁和乐观锁

  1. 判断 商品库存和要购买的数量

  2. 悲观锁:考虑 在修改库存之前,一定有人会打扰,因此使用 锁保护起来,同一时刻,只允许一个写入操作执行

  3. 乐观锁:考虑 在修改数据之前,没有人打扰,因此只是,在修改时,多了一步判断,判断原始数据是否被修改

流程:

        
        with transaction.atomic():    # 生成新的订单 (开启事务)
            point = transaction.savepoint()   # 生成回滚节点

# # 创建订单

                transaction.savepoint_rollback(point)  # 回滚

# # 返回响应 订单生成失败


# 生成订单,修改总金额和总数量


        transaction.savepoint_commit(point)  # 提交事务

# # 删除后端redis数据库中:购物车数据,选中的商品

# # 生成支付地址

应用(1):

class OrderAPIView(APIView):
	@check_login
    def post(self, request):
        # 1. 获取参数: 收货地址id、支付方式
        products_list = request.data.get("productsArr")
        addr_id = request.data.get('addrID')
        pay_method = request.data.get('payMethod')
        user = request.user

        # 2. 组织数据
        try:
            addr = Address.objects.get(id=addr_id)
        except Exception as e:
            return Response({"code":204, 'msg': '地址不存在'})

        if pay_method not in ['0', '1']:
            return Response({'msg': '支付方式不合法'}, status=400)

        # 订单编号
        order_id = datetime.now().strftime('%Y%m%d%H%M%S') + ('%09d' % user.id)  # 根据时间和用户id, 生成唯一的订单id
		total_amount = 0
		total_count = 0
		freight = 10
		# 3. 开启事务
        with transaction.atomic():  # 开启事务
		
			# 创建事务回滚点
            point = transaction.savepoint()
            
			# 4. 创建订单
			try:
                order = Order.objects.create(id=order_id, user=user,  addr=addr, total_amount=total_amount, total_count=total_count, freight=10, pay_method=pay_method,pay_status="0")  # 保存订单,生成订单对象

                # 5. 循环遍历所有加购的商品,将具体的商品信息存入 订单商品表
                for product in products_list:
                    try:
                        good = Good.objects.get(id=product.get("productID"))  # 通过商品id,去mysql中查询 对应的商品信息
                        origin_stock = good.stock
                        origin_count = good.count
                    except:
                    	return Response({"code":204, "msg":"商品已下架"})

                    if product.get("num") > good.stock:
           				# 回滚事务
                        transaction.savepoint_rollback(point)  # 订单不足,下单失败,需要先回滚,后返回信息
                        return Response({"code":204, 'msg': '库存不足'})

					# 6. 判断是否有其他人修改商品库存
                    n = Good.objects.filter(id=product.get("productID"), stock=origin_stock, count=origin_count)
                    # 修改库存之前,先判断库存是否为原始库存, 假设没有任何一条记录被修改,n就是0, n代表多少条数据被修改
                    if not n:
                            raise ValueError("前后数据不一致,创建订单失败")
                    # 将订单中的每个商品信息存入 订单商品表
                    OrderGood.objects.create(order=order, good=good, count=product.get("num"), price=good.selling_price)
                    good.stock = origin_stock - product.get("num")
                    good.count = origin_count + product.get("num")
                    good.save()
                    total_count += product.get("num")  # 对每个商品的数量累加求和
                    total_amount += good.selling_price * product.get("num")  # 对每个商品的小计累加求和

                    
				# 最后价格累加邮费
								total_amount += freight
                order.total_amount = total_amount
                order.total_count = total_count
                order.save()  # 等到 循环累加完之后,订单保存一次即可
				
            except Exception as e:
                transaction.savepoint_rollback(point)  # 下单失败,事务进行回滚,保证原始数据不变

                return Response({'msg': "订单提交失败"}, status=500)
			# 提交事务
            transaction.savepoint_commit(point)  # 所有操作成功,提交事务

        # 移除redis中所有已经下过订单的商品信息
        redis_cli = redis.Redis(host="localhost", port=6379, db=0, password='laufing')
        for i in products_list:
            redis_cli.hdel('cart:%s' % user.id, i.get("productID"))  # 购物车中选中的商品id以及对应的数量
            redis_cli.srem('cart_selected:%s' % user.id, i.get("productID"))  # 移除 集合中所有选中商品的id
        redis_cli.close()

        # 6. 返回订单信息
        return Response({"code":201, "msg":"创建订单成功"})

应用(2):

class OrderInfo(APIView):

    def post(self, request):
        # 验证用户是否登录
        try:
            user_info = request.user_info
            user = User.objects.get(id=user_info.get("id"))
        except Exception as e:
            print(e)
            return Response({
                "code": 204,
                "msg": "用户未登录"
            })

        # 提取前端提交的是数据
        products_list = request.data.get("productsArr")
        addr_id = request.data.get("addrID")
        pay_method = request.data.get("payMethod")

        # 更新商品信息库存减少销量增加
        total_amount = 0  # 总价
        total_count = 0  # 总数量

        # 3.重组订单的数据,生成新的订单 (开启事务)
        with transaction.atomic():
            point = transaction.savepoint()  # 生成回滚节点

            order_id = datetime.datetime.now().strftime("%Y%m%d%H%M%S") \
                       + "_%s" % user.id  # 生成订单号
            try:
                order_data = Order.objects.create(
                    order_id=order_id,
                    user=user,
                    address_id=addr_id,
                    total_amount=total_amount,
                    freight=10,
                    total_count=total_count,
                    pay_method=pay_method,
                    pay_status=0
                )
            except Exception as e:
                print(e)

                transaction.savepoint_rollback(point)  # 回滚

                return Response({
                    "code": 204,
                    "msg": "生成订单失败!"
                })

            for i in products_list:

                # 4.获取勾选商品信息,判断库存是否充足
                good_data = Goods.objects.get(id=i.get("productID"))

                # 乐观锁:获取初始的商品库存和数量
                origin_stock = good_data.stock
                origin_count = good_data.count

                if good_data.stock < i.get("num"):

                    transaction.savepoint_rollback(point)  # 回滚
                    return Response({
                        "code": 204,
                        "msg": "%s库存不足!" % good_data.sku_name
                    })

                # 5.更新商品信息:库存减少,销量增加

                total_amount += i.get("price") * i.get("num")
                total_count += i.get("num")

                # 乐观锁:操作数据时,看一下数据是否与原始数据一致
                try:
                    good_data = Goods.objects.get(id=i.get("productID"),
                                                  stock=origin_stock,
                                                  count=origin_count)
                except Exception as e:
                    print(e)

                    transaction.savepoint_rollback(point)  # 回滚
                    return Response({
                        "code": 204,
                        "msg": "修改商品信息失败!"
                    })
                good_data.stock -= i.get("num")
                good_data.count += i.get("num")
                good_data.save()

                # 6.生成订单商品
                try:
                    OrderGoods.objects.create(
                        good_id=int(i.get("productID")),
                        count=i.get("num"),
                        price=i.get("price"),
                        score=5,
                        order=order_data
                    )
                except Exception as e:
                    print(e)
                    transaction.savepoint_rollback(point)  # 回滚
                    return Response({
                        "code": 204,
                        "msg": "生成订单中商品详情失败!"
                    })

        # 修改总金额和总数量
        order_data.total_amount = total_amount
        order_data.total_count = total_count
        order_data.save()

        transaction.savepoint_commit(point)  # 提交事务

        # 7. 删除后端redis数据库中:购物车数据,选中的商品
        cart_key = "cart_%s" % user.username
        cart_selected_key = "cart_selected_%s" % user.username
        r = redis.Redis(host="127.0.0.1", port=6379, db=0)
        for i in products_list:
            r.hdel(cart_key, i.get("productID"))  # 删除购物车中的数据
            r.srem(cart_selected_key, i.get("productID"))  # 删除选中的商品
        # r.close()

        # 生成支付地址
        url = get_alipay_url(order_id, total_amount)
        return Response({
            "code": 200,
            "msg": "生成订单成功!",
            "url": url
        })

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值