26 May 2010

Django manual-transaction fun

If you use the @commit_manually decorator to manage a transaction, Django will throw a TransactionManagementError ("Transaction managed block ended with pending COMMIT/ROLLBACK") if you fail to commit or rollback the transaction before you exit the function.

If you exit the function due to an exception, Django will sometimes show you the exception that caused the exit. But sometimes it will not: it will only show you the TransactionManagementError, leaving you scratching your head. I've discovered that you get the TransactionManagementError if you modify an object via Django's object-relational mapper. You get the the underlying exception if you modify the database via a raw SQL query. Example:



in views.py:

@commit_manually
def test_view(request):
cur = connection.cursor()
cur.execute("insert into quux (val) values ('a')")
tmodel = TestModel(somenum = 5)
tmodel.save()
assert 0
transaction.rollback()
return HttpResponse("id: %d" % val_id)


in models.py:

class TestModel(models.Model):
somenum = models.IntegerField()

manually-created InnoDB table:

CREATE TABLE `quux` (
`id` integer primary key AUTO_INCREMENT,
`val` text
) ENGINE=InnoDB;
If you view test_view, you will get a TransactionManagementError. If you move the "assert 0" above the tmodel.save(), you get an AssertionError. Raw SQL queries are fine, but you get confusing results if you use the mapper.

If you comment out tmodel.save() but call transaction.set_dirty(), as you're supposed to when you modify the database with raw SQL, you get a TransactionManagementError.

Lesson: if you're getting unexpected TransactionManagementErrors, make sure your code isn't throwing exceptions. Quick fix to see the underlying exception: comment out the @commit_manually decorator. You will lose transactional integrity, so don't do this on a production system.

No comments:

Post a Comment

About Me

blog at barillari dot org Older posts at http://barillari.org/blog