Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

django: Proper way to recover from IntegrityError

What's the proper way to recover from an IntegrityError, or any other errors that could leave my transactions screwed up without using manual transaction control?

In my application, I'm running into problems with IntegrityErrors that I want to recover from, that screw up later database activity, leaving me with:

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

for all database activity after ignoring IntegrityErrors.

This block of code should reproduce the error I'm seeing

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

According to the docs, the solution to recover from an IntegrityError is by rolling back the transaction. But the following code results in a 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

EDIT: I'm confused by the message from the TransactionManagementError, because if in my except I do a:

connection._cursor().connection.rollback()

instead of the django transaction.rollback(), the MyModel.objects.all() succeeds, which doesn't make sense if my code "isn't under transaction management". It also doesn't make sense that code that isn't under transaction management (which I assume means it's using autocommit), can have transactions that span multiple queries.

EDIT #2: I'm aware of using manual transaction control to be able to recover from these errors, but shouldn't I be able to recover without manual transaction control? My understanding is that if I'm using autocommit, there should only be one write per transaction, so it should not affect later database activity.

EDIT #3: This is a couple years later, but in django 1.4 (not sure about later versions), another issue here was that Model.objects.bulk_create() doesn't honor autocommit behavior.

Versions:

  • Django: 1.4 (TransactionMiddleWare is not enabled)
  • Python: 2.7
  • Postgres: 9.1
like image 804
notbad.jpeg Avatar asked Jun 29 '14 02:06

notbad.jpeg


People also ask

What is Integrityerror in Django?

This means that your DB is expecting that field to have a value. So when it doesn't you get an error. null. If True, Django will store empty values as NULL in the database. Default is False.


1 Answers

Django's default commit mode is AutoCommit. In order to do rollback, you need to wrap the code doing the work in a transaction. [docs]

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

To get database level autocommit, you will require the following option in your DATABASES settings dictionary.

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

Alternately, you can use explicit savepoints to roll back to. [docs]

@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()
like image 151
Thomas Avatar answered Oct 15 '22 06:10

Thomas