[Fixed]-Django transaction.atomic() guarantees atomic READ + WRITE?

17👍

Django’s transaction.atomic() is a thin abstraction over the transaction facilities of the database. So its behavior really depends on the database layer, and is specific to the type of database and its settings. So to really understand how this works you need to read and understand the transaction documentation for your database. (In PostgreSQL, for example, the relevant documentation is Transaction Isolation and Explicit Locking).

As for your specific test case, the behavior you want can be achieved by using the select_for_update() method on a Django queryset (if your database supports it). Something like:

in atomic_test.py

with transaction.atomic():
    t = Test.objects.filter(id=1).select_for_update()[0]
    sleep(10) 
    t.value = t.value + 10
    t.save()

in atomic_test2.py

with transaction.atomic():
    t = Test.objects.filter(id=1).select_for_update()[0]
    t.value = t.value - 20
    t.save()

The second one should block until the first one finishes, and see the new value of 60.

Other options include using the SERIALIZABLE transaction isolation level or using a row lock, though Django doesn’t provide a convenient API for doing those things.

3👍

It would be much better to update atomically on the database using the F function:

from django.db.models import F
Test.objects.filter(id=1).update(value=F("value") + 10)

(This generates SQL something like "UPDATE test_test SET value = value + 10 WHERE id = 1")

Leave a comment