【问题标题】:Is it ok to catch and reraise an exception inside Django transaction.atomic()?可以在 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
    

【问题讨论】:

    标签: python django transactions django-orm


    【解决方案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,这不容易回答。类上方的注释非常有用。您可能想在调试会话中深入挖掘或等待已经知道的人。
      猜你喜欢
      • 2011-09-12
      • 2014-07-09
      • 2014-07-29
      • 2013-01-09
      • 2017-01-12
      • 1970-01-01
      • 1970-01-01
      • 2017-04-07
      • 1970-01-01
      相关资源
      最近更新 更多