Django:正确处理IntegrityError的方法

8 投票
1 回答
4060 浏览
提问于 2025-04-18 11:27

如何正确处理IntegrityError或其他可能导致事务混乱的错误,而不使用手动事务控制?

在我的应用程序中,我遇到了IntegrityError的问题,我想要解决这些问题,因为它们会影响后续的数据库操作,导致我遇到:

DatabaseError: current transaction is aborted, commands ignored until end of transaction block` 

在忽略IntegrityErrors后,所有的数据库活动都会受到影响。

这段代码应该能重现我遇到的错误

from django.db import transaction

try:
    MyModel.save() # Do a bad save that will raise IntegrityError
except IntegrityError:
    pass

MyModel.objects.all() # raises DatabaseError: current transaction is aborted, commands ignored until end of transaction block

根据文档,解决IntegrityError的方法是回滚事务。但是,下面的代码却导致了TransactionManagementError

from django.db import transaction

try:
    MyModel.save()
except IntegrityError:
    transaction.rollback() # raises TransactionManagementError: This code isn't under transaction management

MyModel.objects.all() # Should work

编辑:我对TransactionManagementError的提示感到困惑,因为如果在我的except中我执行:

connection._cursor().connection.rollback()

而不是使用django的transaction.rollback(),那么MyModel.objects.all()就能成功,这让我觉得很奇怪,如果我的代码“没有在事务管理之下”。而且,似乎也不合理的是,未在事务管理之下的代码(我认为这意味着它在使用自动提交)却可以有跨多个查询的事务。

编辑 #2:我知道可以使用手动事务控制来处理这些错误,但难道我不应该能够在使用手动事务控制的情况下恢复吗?我理解的是,如果我使用自动提交,每个事务应该只有一次写入,所以这不应该影响后续的数据库活动。

编辑 #3:这是几年前的事了,但在django 1.4(不确定后续版本)中,另一个问题是Model.objects.bulk_create()并不遵循自动提交的行为。

版本:

  • Django: 1.4 (TransactionMiddleWare 启用)
  • Python: 2.7
  • Postgres: 9.1

1 个回答

3

Django默认的提交模式是自动提交(AutoCommit)。如果你想要进行回滚操作,就需要把执行工作的代码放在一个事务中。 [文档]

with transaction.commit_on_success():
    # Your code here. Errors will auto-rollback.

要实现数据库级别的自动提交,你需要在你的DATABASES设置字典中添加以下选项。

'OPTIONS': {'autocommit': True,}

另外,你也可以使用明确的保存点来进行回滚。 [文档]

@transaction.commit_manually
def viewfunc(request):

  a.save()
  # open transaction now contains a.save()
  sid = transaction.savepoint()

  b.save()
  # open transaction now contains a.save() and b.save()

  if want_to_keep_b:
      transaction.savepoint_commit(sid)
      # open transaction still contains a.save() and b.save()
  else:
      transaction.savepoint_rollback(sid)
      # open transaction now contains only a.save()

  transaction.commit()

撰写回答