可以在 Django transaction.atomic() 中捕获并重新引发异常吗?

Posted

技术标签:

【中文标题】可以在 Django transaction.atomic() 中捕获并重新引发异常吗?【英文标题】:Is it ok to catch and reraise an exception inside Django transaction.atomic()? 【发布时间】:2017-07-24 00:48:17 【问题描述】:

Django 的文档对transaction.atomic() 和异常有这样的说法:

https://docs.djangoproject.com/en/1.10/topics/db/transactions/#django.db.transaction.atomic

避免在 atomic 中捕获异常!

退出原子块时,Django 会查看它是正常退出还是异常退出,以确定是提交还是回滚。如果您在原子块内捕获并处理异常,您可能会向 Django 隐藏发生问题的事实。这可能会导致意外行为。

这主要是对 DatabaseError 及其子类(例如 IntegrityError)的关注。在这样的错误之后,事务被破坏,Django 将在原子块的末尾执行回滚。如果您尝试在回滚发生之前运行数据库查询,Django 将引发 TransactionManagementError。当 ORM 相关的信号处理程序引发异常时,您也可能会遇到此行为。

捕获数据库错误的正确方法是围绕原子块,如上所示。如有必要,为此添加一个额外的原子块。这种模式还有另一个优点:它明确界定了发生异常时将回滚的操作。

如果您捕获原始 SQL 查询引发的异常,则 Django 的行为是未指定的并且依赖于数据库。

这样做可以吗?或者这会导致“意外行为”吗?

with transaction.atomic():
    # something
    try:
        # something
    except:
        logger.exception("Report error here.")
        raise
    

【问题讨论】:

【参考方案1】:

这个例子将消除你的疑惑。

with transaction.atomic():
    try:
        # if you do something that raises ONLY db error. ie. Integrity error
    except Exception:
        # and you catch that integrity error or any DB error like this here.
        # now if you try to update your DB
        model = Model.objects.get(id=1)
        model.description = "new description"
        model.save()
        # This will be illegal to do and Django in this case 
        # will raise TransactionManagementError suggesting 
        # you cannot execute any queries until the end of atomic block.

现在,如果您像这样提出自定义异常:

with transaction.atomic():
    try:
        # raising custom exception
        raise Exception("some custom exception")
    except Exception:
        # and you catch that exception here
        # now if you try to update your DB
        model = Model.objects.get(id=1)
        model.description = "new description"
        model.save()
        # Django will allow you to update the DB.

【讨论】:

【参考方案2】:

这样做可以吗?或者这会导致“意外行为”吗?

with transaction.atomic():
    # something
    try:
        # something
    except:
        logger.exception("Report error here.")
        raise

除了简单的 except 子句(您至少需要 except Exception:),这没关系,假设您的记录器不接触数据库(无论如何这将是一个非常糟糕的主意)并且记录器调用不会引发另一个例外(在这种情况下,我不知道实际会发生什么)。

但是你会得到相同的结果,反转 transaction.atomic() 块和 try/except,即:

try:
    with transaction.atomic():
        # something
        # something
except Exception:
    logger.exception("Report error here.")
    raise

【讨论】:

嘿,远射。在进行单元测试时有什么想法,在这种情况下我如何测试除了块?【参考方案3】:

根据文档,我将确保重新引发正确的异常,您可以独立处理其他错误。对于 django,它只需要在与数据库通信时收到有关出错的通知。

with transaction.atomic():
    # something
    try:
        # something
    except DatabaseError as db_err:
        logger.exception("Report error here.")
        raise db_err
    except Exception:
        # do something else
        # no need to reraise
        # as long as you handle it properly and db integrity is guaranteed

【讨论】:

但随后他们做出了模糊的威胁,例如“这主要是 DatabaseError 及其子类的问题......”。大多? MOSTLY 我该怎么办? 我同意 - 另一方面,这个话题远非简单,我知道开发人员在写下内容时会很小心。我尝试阅读the code,但没有深入了解django.db,这不容易回答。类上方的注释非常有用。您可能想在调试会话中深入挖掘或等待已经知道的人。

以上是关于可以在 Django transaction.atomic() 中捕获并重新引发异常吗?的主要内容,如果未能解决你的问题,请参考以下文章

django-mutant 在 django-admin 中创建模型

我可以在 Django 设置中定义类吗?如何在测试中覆盖这些设置?

(转)Django学习之 第二章:Django快速上手

是否有任何工具可以自动在Django中为模型创建模板

django url中的name参数怎么理解

在 Django 中,是不是可以迁移任何特定的特定文件?