【Django】transaction事务的使用、乐观锁与悲观锁

1.事务的四大特性

(1)原子性:事务中的所有操作是一个不可分割的整体
(2)一致性:数据变更要保持一致
(3)隔离性:多个事务对数据的修改操作,对彼此间的可见性
(4)持久性,提交后保存到硬盘永久存储

2.事务的隔离级别

事务有隔离性,根据隔离级别不同,事务彼此间的可见性不同
从高到低依次为串行事务(Serializable)、可重复读(Repeatable-Read)、读已提交(Read-Committed)、读未提交(Read-Uncommitted)
在这里插入图片描述

2.1 四种级别对比

串行事务

可重复读
读已提交
读未提交

2.2 修改MySQL的隔离级别

打开MySQL的配置文件,例如

sudo vim /etc/mysql/mysql.conf.d/mysqld.cnf

在文件尾部追加transaction-isolation,注意级别用大写字母,例如

# 隔离级别越高数据越安全,越低
transaction-isolation=READ-COMMITTED

重启MySQL

sudo /etc/init.d/mysql restart

3.Django事务的使用方法

3.1 导包

Django提供了一个transaction模块来管理事务

from django.db import transaction
3.2 开启事务

有两种开启事务的方法,装饰器和with语句

from django.db import transaction

# 方法一:装饰器
@transaction.atomic
def viewfunc(request):
  # 这些代码会在一个事务中执行
  ......

# 方法二:with语句
def viewfunc(request):
  # 这部分代码不在事务中
  ......
  with transaction.atomic():
      # 这部分代码会在事务中执行
      ......

使用装饰器虽然简单便捷,但是会把整个函数都当做事务,这样有时候会很不方便,所以在项目中使用with语句的方法显得更灵活

3.3 事务操作

事务的操作包括提交、回滚,在此之前得先有个保存点来记录数据的状态

from django.db import transaction

# 1.使用with或装饰器开启事务
with transaction.atomic()
	# 2.创建保存点
	save_id = transaction.savepoint()  
	# 3.回滚到保存点
	transaction.savepoint_rollback(save_id)
	# 4.提交事务
	transaction.savepoint_commit(save_id)

4.举例

比如说用户下单购买商品

class OrderCommitView(View):
    def post(self, request):
        ......
        from django.db  import transaction
        # 开始事务,使用with语句
        with transaction.atomic():
            # 创建事务保存点
            save_id = transaction.savepoint()
            try:
                order = OrderInfo.objects.create(
             		...
                )
                ...
                for sku_id in sku_ids:
                    sku = SKU.objects.get(id=sku_id)
                    sku_count = carts_dict[sku_id]['count']
                    if sku_count > sku.stock:
                        # 库存不足,事务回滚到之前的保存点
                        transaction.savepoint_rollback(save_id)
                        return JsonResponse({
    
    'code': 400, 'errmsg': '库存不足'})
                    ...
                order.total_amount += order.freight
                order.save()
            except:
            	# 若出现异常,回滚事务到保存点
                transaction.savepoint_rollback(save_id)
                return JsonResponse({
    
    'code': 0, 'errmsg': '下单失败'})

            # 若一切正常,提交保存事务
            transaction.savepoint_commit(save_id)
		...
        return JsonResponse({
    
    'code': 0, 'errmsg': '下单成功', 'order_id': order.order_id})

5.乐观锁与悲观锁

  • 悲观锁
    就是一种对数据的修改持有悲观态度的并发控制方式,总认为自己在执行过程中会被人介入,所以会上锁。传统的关系型数据库一般都会在操作前上锁,比如说行锁,表锁等,读锁,写锁等

悲观锁又分为共享锁和排他锁
共享锁(读锁即S锁)就是多个事务共享,但只能读不能改,排他锁(写锁即X锁)就是不能与其他锁共存

  • 乐观锁
    乐观锁并非真的上锁,而是对操作持乐观态度,认为在操作的时候不会被介入。并非真的锁,只是在写之前先查一下数据是否与之前读的一样,若一样则说明没有被修改,若不一样则已被修改

我们使用while循环完成乐观锁业务

class OrderCommitView(LoginRequiredJSONMixin, View):
    def post(self, request):
		...
		# 开启事务
        with transaction.atomic():
        	# 设置保存带你
            save_id = transaction.savepoint()
            order = OrderInfo.objects.create(
            	...
            )

            for sku_id in sku_ids:
				# 开启乐观锁机制
                while True:
                    # 购物车中的sku商品
                    sku = SKU.objects.get(pk=sku_id)
                    # (1)、读旧的库存和销量
                    old_stock = sku.stock
                    old_sales = sku.sales
                    
                    count = cart_dict[sku_id]['count']
                    if count > old_stock:
                        # 库存不足, 回滚事务
                        transaction.savepoint_rollback(save_id)
                        return JsonResponse({
    
    'code': 400, 'errmsg': '库存不足'}, status=400)
                    # (2)、基于旧库存和销量计算新值
                    new_stock = old_stock - count
                    new_sales = old_sales + count
                    # (3)、在旧库存和销量基础上,查询后修改
                    ret = SKU.objects.filter(
                        pk=sku.id,
                        stock=old_stock,
                        sales=old_sales
                    ).update(stock=new_stock, sales=new_sales)
                    # 如果update函数返回值为0,说明filter过滤结果为空,说明根据旧数据找不到原有的sku,即有别的事务介入
                     # 如果ret不为0,说明正确更新,跳出循环
                    if ret:
                        break
                # 中间表OrderGoods表中插入一条数据,表示新建的订单中的sku商品
                OrderGoods.objects.create(
   					...
                )
				...
            order.total_amount += freight
            order.save()

            # 若一切正常,提交保存点(删除保存点)
            transaction.savepoint_commit(save_id)
        return JsonResponse({
    
     'code': 0, 'errmsg': 'ok', 'order_id': order_id })

关于MySQL事务操作可以查看【mysql】mysql引擎和事务

猜你喜欢

转载自blog.csdn.net/qq_39147299/article/details/108852687