可以在 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 中创建模型