Java自学者论坛

 找回密码
 立即注册

手机号码,快捷登录

恭喜Java自学者论坛(https://www.javazxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,会员资料板块,购买链接:点击进入购买VIP会员

JAVA高级面试进阶训练营视频教程

Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程Go语言视频零基础入门到精通Java架构师3期(课件+源码)
Java开发全终端实战租房项目视频教程SpringBoot2.X入门到高级使用教程大数据培训第六期全套视频教程深度学习(CNN RNN GAN)算法原理Java亿级流量电商系统视频教程
互联网架构师视频教程年薪50万Spark2.0从入门到精通年薪50万!人工智能学习路线教程年薪50万大数据入门到精通学习路线年薪50万机器学习入门到精通教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程MySQL入门到精通教程
查看: 592|回复: 0

Django transaction 误用之后遇到的一个问题与解决方法

[复制链接]
  • TA的每日心情
    奋斗
    2024-11-24 15:47
  • 签到天数: 804 天

    [LV.10]以坛为家III

    2053

    主题

    2111

    帖子

    72万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    726782
    发表于 2021-4-25 16:42:04 | 显示全部楼层 |阅读模式

    今天在调试项目开发好的一个模块的时候,发现了一个很诡异的现象,最后追踪发现是因为在项目中事务处理有误所致。这个问题坑了我好一会,所以记录一下,以免再踩坑。下面开始详述。

    我们都知道 Django 框架提供了很多的开启事务的方式,这在后面会有详述。笔者比较喜欢使用的是使用 @transaction.atomic 装饰的方式来启动一个事务。因为通过该形式,我们可以在保证了 db 原子操作的同时,还可以自定义事务涉及的模块范围。atomic 还可以通过上下文的形式来使用,比如:

    with transaction.atomic():
        transaction_plalala()
    .
    .
    .
    

    好了。既然如此,那就用起来吧。一阵啪啪啪之后,开发完了,调试的 log 也打了,开测吧。诡异的事情在此发生了。在log中明明打印出来了数据库中生成的主键id,但是在数据库中死活查不出来。WTF?!通过 SHOW CREATE TABLE xxx 也看到了 xxx 表的自增值已经发现了变化。但咋就没有了呢?谁动了我的数据?这时候,不得不从 view 开始看起,一直追踪到了开发的新的模块。view到调用模块没有问题。这是什么情况?有鬼?肯定不是。莫非在某个地方,配置了一个新的事务,而这个事务是包含了整个 view?因为笔者只发现了 view 中有个 raise exception的操作。猜测只能是这样了。因为我新开发的模块没有问题的,这我是在其他的 view 中进行过验证的。

    于是乎,笔者看了下 Django 中的事务开启的方式,发现了果然有一个将事务绑定到 HTTP 请求上的开启的方式。它的开启方式是在 db 中指定 ATOMIC_REQUESTS=True 开启。打开 settings 文件,找到了对应的配置,果然,问题就是出在这里!咋办?既然这边人家已经配置了,在不影响到其他 view(说不定已经依赖了此事务操作)的情况下,怎么去关闭这个配置呢?答案是通过 transaction.non_atomic_requests 装饰view。好了。测试一下,确实可以了。问题解决了。下面的是官方 Demo:

    from django.db import transaction
    
    @transaction.non_atomic_requests
    def my_view(request):
        do_stuff()
    
    @transaction.non_atomic_requests(using='other')
    def my_other_view(request):
        do_stuff_on_the_other_database()
    

    在看 Django 中的官方文档后,发现,其不推荐这么做。原因是如果将事务跟 HTTP 请求绑定到一起的话,view 是依赖于应用程序对数据库的查询语句效率和数据库当前的锁竞争情况。当流量上来的时候,性能会有影响。那么,该怎么去保证既可以使用事务呢?

    第一种就是上面所说的这种,在 database 中通过指定 ATOMIC_REQUESTS 的形式来将事务绑定到HTTP请求上。接触 view 在事务中的操作的方式是在 view 上装饰 transaction.non_atomic_requests,前面已经说过,具体也可以阅读 Django 官方文档。

    default_db = {
            "ENGINE": "",
            "NAME": "",
            "USER": "",
            "PASSWORD": "",
            "HOST": "",
            "PORT": "",
            "OPTIONS": "",
            "ATOMIC_REQUESTS": True,
        }
    

    还有一种方式是笔者喜欢用的那种,通过 transaction.atomic 来更加明确的控制事务。atomic允许我们在执行代码块时,在数据库层面提供原子性保证。 如果代码块成功完成, 相应的变化会被提交到数据库进行commit;如果执行期间遇到异常,则会将该段代码所涉及的所有更改回滚。
    这里,我们在使用此方式的时候还需要注意一点,就是:避免在 atomic里捕获异常!当一个原子块执行完退出时,Django会审查是正常提交还是回滚。如果你在原子块中捕获了异常的句柄, 你可能就向 Django 隐藏了问题的发生。这可能会导致意想不到的后果。正确捕捉数据库异常应该是类似上文所讲 ,基于atomic 代码块来做。若有必要,可以额外增加一层atomic代码来用于此目的。这种模式还有另一个优势:它明确了当一个异常发生时,哪些操作将回滚。在底层,Django的事务管理代码:

    • 当进入到最外层的 atomic 代码块时会打开一个事务;
    • 当进入到内层atomic代码块时会创建一个保存点;
    • 当退出内部块时会释放或回滚保存点;
    • 当退出外部块时提交或回退事物。
      你可以通过设置savepoint 参数为 False来使对内层的保存点失效。如果异常发生,若设置了savepoint,Django会在退出第一层代码块时执行回滚,否则会在最外层的代码块上执行回滚。 原子性始终会在外层事物上得到保证。这个选项仅仅用在设置保存点开销很明显时的情况下。它的缺点是打破了上述错误处理的原则。
    from django.db import transaction
    
    def viewfunc(request):
        # This code executes in autocommit mode (Django's default).
        do_stuff()
    
        with transaction.atomic():
            # This code executes inside a transaction.
            do_more_stuff()
    

    Django 支持 autocommit 来为每个SQL语句在执行时都会启动一个事务。如果想要关闭,可以通过在配置文件中设置 AUTOCOMMIT=False 参数来关闭。这样,Django 将不能启用 autocommit,也不能执行任何 commits。这就需要你对每个事物执行明确的commit操作。因此,这最好只用于你自定义的事物控制中间件或者是一些比较奇特的场景。

    参考:

    哎...今天够累的,签到来了1...
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|小黑屋|Java自学者论坛 ( 声明:本站文章及资料整理自互联网,用于Java自学者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-12-22 17:01 , Processed in 0.060687 second(s), 29 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表