Django - 事务

1.1 局部事务(*)

基于上下文管理,如果出现异常则自动回滚;无异常则自动提交。

from rest_framework.views import APIView
from rest_framework.response import Response
from django.db import transaction
from api import models


class Demo1View(APIView):
    def get(self, request, *args, **kwargs):
        try:
            with transaction.atomic():
                models.UserInfo.objects.create(name='v1', age=1)
                models.Order.objects.create(name='v1', age=1)
        except Exception as e:
            print("异常,自动回滚")

        return Response("...")

事务提交的回调函数(本质上就是事务完成后,自动执行一个函数):

from rest_framework.views import APIView
from rest_framework.response import Response
from django.db import transaction
from api import models
from functools import partial


def db_success_callback(*args, **kwargs):
    print(args, **kwargs)

class Demo1View(APIView):
    def get(self, request, *args, **kwargs):
        try:
            with transaction.atomic():
                # 回调函数,事务正常提交自动执行
                transaction.on_commit(db_success_callback)
                transaction.on_commit( partial(db_success_callback, 11, 22, 33) )
                # 回调函数出现异常,事务是不会回滚的,回调和事务无关.
                models.UserInfo.objects.create(name='v1', age=1)
                models.Order.objects.create(title='v1', count=1)
        except Exception as e:
            print("异常,自动回滚") # on_commit回调函数内部异常时不会回滚

        return Response("...")

回滚到 指定事务点:

from rest_framework.views import APIView
from rest_framework.response import Response
from django.db import transaction
from api import models


class Demo1View(APIView):
    def get(self, request, *args, **kwargs):
        try:
            with transaction.atomic():
                # 回调函数,事务正常提交自动执行
                n1 = transaction.savepoint()
                models.UserInfo.objects.create(name='v1', age=1)
                n2 = transaction.savepoint()
                models.UserInfo.objects.create(name='v2', age=1)

                # 必须在事务里面,回顾到指定 事务点,后续东西不提交
                transaction.savepoint_rollback(n2)
        except Exception as e:
            print("异常,自动回滚", e)  # on_commit回调函数内部异常时不会回滚

        return Response("...")

1.2 视图事务

针对整个视图进行开启事务:

  • 视图内,有数据库操作异常,自动回滚
  • 视图内,有其他异常,不会回滚。
from rest_framework.views import APIView
from rest_framework.response import Response
from django.db import transaction, IntegrityError
from api import models


class Demo1View(APIView):
	  # CBV中如果要用装饰器一定要加在self.dispatch上, 如果是事务也可以加在get  post 等方法上, 
    @transaction.atomic
    def get(self, request, *args, **kwargs):
        try:
            models.UserInfo.objects.create(name='v100', age=1)
            models.UserInfo.objects.create(name="v200", age="xxx")  # 数据库有异常,回滚,即:v100不会保存
            int("asdf")   # 非数据库有异常,不会回滚,即:两条数据正常保存到数据库
        except Exception as e:
            pass
        return Response("...")

    
    
@method_decorator(transaction.atomic, name='dispatch')   #给dispatch加装饰器,装饰器名字transaction.atomic
class Demo1View(APIView):

    def get(self, request, *args, **kwargs):
        models.UserInfo.objects.create(name='v100', age=1)
        models.UserInfo.objects.create(name="v200", age="xxx") # 报错
        return Response("...")      

定义事务点,自定义回滚位置:

from rest_framework.views import APIView
from rest_framework.response import Response
from django.db import transaction, IntegrityError
from api import models


class Demo1View(APIView):

    @transaction.atomic
    def get(self, request, *args, **kwargs):
        try:
            models.UserInfo.objects.create(name='v10', age=1)
            n1 = transaction.savepoint()
            models.UserInfo.objects.create(name="v11", age=1)
            n2 = transaction.savepoint()
            models.UserInfo.objects.create(name='v12', age=1)
            n3 = transaction.savepoint()
            models.UserInfo.objects.create(name='v13', age=1)
            
            # 后续读取到某些值后,发现 v12不应该创建,那么就可以主动回滚
            transaction.savepoint_rollback(n1)
        except Exception as e:
            print("有异常", e)
        return Response("...")

1.3 全局事务

效率低:项目中一般不会使用。

如果想要开启全局事务,需要在连接数据库时多设置一个参数:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'dbhot4',
        'USER': 'root',
        'PASSWORD': 'root123',
        'HOST': '127.0.0.1',
        'PORT': '3306',
        'ATOMIC_REQUESTS': True
    }
}
  • 只要视图函数执行异常,无论是什么原因触发,均自动回滚。

    class Demo1View(APIView):
        def get(self, request, *args, **kwargs):
            models.UserInfo.objects.create(name='v1', age=1)
            models.UserInfo.objects.create(xxxxxxx='v2', age=1) # 错误
            return Response("...")
    
    class Demo1View(APIView):
        def get(self, request, *args, **kwargs):
            models.UserInfo.objects.create(name='v1', age=1)
            models.UserInfo.objects.create(name='v2', age=1)
            int("asdf")  # 错误
            return Response("...")
    
  • 如果视图函数执行不报错(try处理异常,也叫不报错),则不会回滚

    class Demo1View(APIView):
        def get(self, request, *args, **kwargs):
            try:
                models.UserInfo.objects.create(name='v1', age=1)
                models.UserInfo.objects.create(xxxxxxx='v2', age=1)
                int("xxx")
            except Exception as e:
                pass
            return Response("...")
    
    # 视图函数执行没有报错,不会回滚。
    

如果开启了全局事务,想要免除某个指定的函数不需要开启事务,则可以使用:

from rest_framework.views import APIView
from rest_framework.response import Response
from django.db import transaction, IntegrityError
from api import models
from django.utils.decorators import method_decorator


@method_decorator(transaction.non_atomic_requests, name='dispatch')
class Demo1View(APIView):

    def get(self, request, *args, **kwargs):
        models.UserInfo.objects.create(name='v100', age=1)
        models.UserInfo.objects.create(name="v200", age="xxx") # 报错
        return Response("...")
      

事务操作 with transaction.atomic():

from django.db import transaction
# 开启事务
with transaction.atomic():
    # 设置保存点,指定代码回滚到哪个位置
    save_id = transaction.savepoint()
    #...
    # 回滚事务
    transaction.savepoint_rollback(save_id)
    #...
    # 提交事务
    transaction.savepoint_commit(save_id)

#################################
"""
排它锁
悲观锁: 当查询某条记录时,即让数据库为该记录加锁,锁住记录后别人无法操作
满足条件: 1.必须在事务中使用
         2.select 后面加 for update
select stock from goods where id =1 for update;
Goods.objects.select_for_update().get(id=1) 
悲观锁什么时候释放? 
要么事务回滚,要么事务提交,也就是当事务结束后才能释放
悲观锁类似于我们在多线程资源竞争时添加的互斥锁,容易出现死锁现象,采用不多.
比如用户A给表A加了锁,然后查询表B,用户B给表B加了锁,然后查询表A,两个人同事等待对方操作完后,解除锁,这样就产生了死锁
"""

@login_required
def order(request):
    redis_cli = get_redis_connection('cart')

    # 1、获取商品状态选中的商品信息
    cart_datas = redis_cli.hgetall(f'cart-{request.user.id}')
    selected_datas = redis_cli.smembers(f'selected-{request.user.id}')

    # 2、从所有商品中过滤出 选中的商品 {495:2, 496:5}
    cart_datas = {int(id): int(num) for id, num in cart_datas.items() if id in selected_datas}

    # 订单号:当前日期时间戳 + 用户ID
    order_code = datetime.datetime.now().strftime('%Y%m%d%H%M%S') + str(request.user.id)

    # 开启事务
    with transaction.atomic():

        # 设置保存点,指定代码回滚到哪个位置
        save_id = transaction.savepoint()

        # 3、生成总订单
        order = AxfOrder.objects.create(
            order_code=order_code,
            uid=request.user.id,
            order_price=0,
            order_num=sum(cart_datas.values()),
        )

        # 4、循环字典中选中商品的数据,生成子订单
        total_price = 0

        for id, num in cart_datas.items():

            # 5、判断库存
            # good = AxfGoods.objects.get(id=id)

            # 采用悲观锁解决高并发day
            good = AxfGoods.objects.select_for_update().get(id=id)

            if good.storenums < num:
                # 回滚事务
                transaction.savepoint_rollback(save_id)
                return HttpResponse(f'商品id是{good.id} 的商品库存不足')
            # 模拟高并发
            time.sleep(5)

            # 6、减库存加销量
            good.storenums -= num
            good.productnum += num
            good.save()


            # 7、生成子订单
            AxfOrderDetail.objects.create(
                good_id=good.id,
                order_code=order_code,
                good_price=good.price,
                good_num=num,
                uid=request.user.id
            )

            # 8、计算订单总价
            total_price += good.price * num

        # 重新给总订单赋值 商品的总价
        order.order_price = total_price
        order.save()

        # 提交事务
        transaction.savepoint_commit(save_id)

    # 9、删除redis中的数据
    redis_cli = get_redis_connection('cart')

    # 从hash删除选中的商品数据
    redis_cli.hdel(f'cart-{request.user.id}', *selected_datas)
    redis_cli.srem(f'selected-{request.user.id}', *selected_datas)

    # TODO 跳到订单支付页面
    return redirect(reverse('order:pay_order', args=[order_code]))

"""
乐观锁:乐观锁并不是真实存在的锁,而是在更新的时候判断此时的库存是否是之前查询出的库存,如果相同,如果相同表示没人修改,可以更新库存,否则表示别人抢过资源,不再执行库存更新
update goods set stock=10 where id=1 and stock=20;
Goods.objects.filter(id=1,stock=20).update(stock=10)
修改MySQL数据库事务隔离级别为:Read committed 读取已提交


操作条件:
库存大于购买量
更新库存和销量时原始库存没变

并发:在一段时间之内,一个cpu执行多个线程, 操作系统控制切换的
并行:多个cpu同是执行多个线程
"""

@login_required
def order(request):
    redis_cli = get_redis_connection('cart')

    # 1、获取商品状态选中的商品信息
    cart_datas = redis_cli.hgetall(f'cart-{request.user.id}')
    selected_datas = redis_cli.smembers(f'selected-{request.user.id}')

    # 2、从所有商品中过滤出 选中的商品 {495:2, 496:5}
    cart_datas = {int(id): int(num) for id, num in cart_datas.items() if id in selected_datas}

    # 订单号:当前日期时间戳 + 用户ID
    order_code = datetime.datetime.now().strftime('%Y%m%d%H%M%S') + str(request.user.id)

    # 开启事务
    with transaction.atomic():

        # 设置保存点,指定代码回滚到哪个位置
        save_id = transaction.savepoint()

        # 3、生成总订单
        order = AxfOrder.objects.create(
            order_code=order_code,
            uid=request.user.id,
            order_price=0,
            order_num=sum(cart_datas.values()),
        )

        # 4、循环字典中选中商品的数据,生成子订单
        total_price = 0

        for id, num in cart_datas.items():

            while True:
                # 5、判断库存
                good = AxfGoods.objects.get(id=id)


                if good.storenums < num:
                    # 回滚事务
                    transaction.savepoint_rollback(save_id)
                    return HttpResponse(f'商品id是{good.id} 的商品库存不足')
                # 模拟高并发
                time.sleep(5)

                # 6、减库存加销量
                # 乐观锁
                res = AxfGoods.objects.filter(id=good.id, storenums=good.storenums).update(
                    storenums=good.storenums - num,
                    productnum=good.productnum + num
                )

                # 没有更新成功
                if not res:
                    continue
                    # transaction.savepoint_rollback(save_id)
                    # return HttpResponse('网络忙,请稍后再试')         


                # 7、生成子订单
                AxfOrderDetail.objects.create(
                    good_id=good.id,
                    order_code=order_code,
                    good_price=good.price,
                    good_num=num,
                    uid=request.user.id
                )

                # 8、计算订单总价
                total_price += good.price * num 
                break

        # 重新给总订单赋值 商品的总价
        order.order_price = total_price
        order.save()

        # 提交事务
        transaction.savepoint_commit(save_id)

    # 9、删除redis中的数据
    redis_cli = get_redis_connection('cart')

    # 从hash删除选中的商品数据
    redis_cli.hdel(f'cart-{request.user.id}', *selected_datas)
    redis_cli.srem(f'selected-{request.user.id}', *selected_datas)

    # TODO 跳到订单支付页面
    return redirect(reverse('order:pay_order', args=[order_code]))


#################################
from django.db import transaction
def comment(request):
    article_id = request.POST.get("article_id")
    pid = request.POST.get("pid")
    content = request.POST.get("content")
    user_id = request.user.pk

    article_obj = models.Article.objects.filter(pk=article_id).first()

    # 事务操作
    with transaction.atomic():
        comment_obj = models.Comment.objects.create(user_id=user_id, article_id=article_id, content=content,
                                                    parent_comment_id=pid)
        models.Article.objects.filter(pk=article_id).update(comment_count=F("comment_count") + 1)

    response = {}

    response["create_time"] = comment_obj.create_time.strftime("%Y-%m-%d %X")
    response["username"] = request.user.username
    response["content"] = content

    # 发送邮件
    from django.core.mail import send_mail
    from cnblog import settings

    # send_mail(
    #     "您的文章%s新增了一条评论内容"%article_obj.title,
    #     content,
    #     settings.EMAIL_HOST_USER,
    #     ["916852314@qq.com"]
    # )

    import threading
    t = threading.Thread(target=send_mail, args=("您的文章%s新增了一条评论内容" % article_obj.title,
                                                 content,
                                                 settings.EMAIL_HOST_USER,
                                                 ["916852314@qq.com"])
                         )
    t.start()
    return JsonResponse(response)
posted @ 2023-12-26 14:45  silencio。  阅读(56)  评论(0)    收藏  举报