I need to make sure that an object that is read from the database and written back, cannot be modified in the meantime by another request/process.
Does transaction.atomic() guarantee that?
My tests so far tell me no. If there's nothing wrong with them what would be the right way to achieve atomic READS and WRITES?
My example that I have tested.
Put the Test class somewhere in your model. atomic_test.py and atomic_test2.py should be saved as management commands. Run python manage.py atomic_test first, then python manage.py atomic_test2. The second script does not block and its changes are lost.
models.py
class Test(models.Model):
value = models.IntegerField()
atomic_test.py
from django.core.management.base import NoArgsCommand
from django.db import transaction
from time import sleep
from core.models import Test
class Command(NoArgsCommand):
option_list = NoArgsCommand.option_list
def handle(self, **options):
Test.objects.all().delete()
t = Test(value=50)
t.save()
print '1 started'
with transaction.atomic():
t = Test.objects.all()[0]
sleep(10)
t.value = t.value + 10
t.save()
print '1 finished: %s' %Test.objects.all()[0].value
atomic_test2.py
from django.core.management.base import NoArgsCommand
from django.db import transaction
from time import sleep
from core.models import Test
class Command(NoArgsCommand):
option_list = NoArgsCommand.option_list
def handle(self, **options):
print '2 started'
with transaction.atomic():
t = Test.objects.all()[0]
t.value = t.value - 20
t.save()
print '2 finished: %s' %Test.objects.all()[0].value
Django provides a single API to control database transactions. Atomicity is the defining property of database transactions. atomic allows us to create a block of code within which the atomicity on the database is guaranteed. If the block of code is successfully completed, the changes are committed to the database.
An atomic transaction is a single, irreducible component of a classic transaction, such as making a purchase. WS-AT ensures that if a single atomic transaction fails, the whole transaction fails: A partial transaction cannot take place.
In SQL databases transaction atomicity is implemented most frequently using write-ahead logging (meaning that the transaction log entries are written before the actual tables and indexes are updated).
No, Django doesn't use Atomic Transaction by default.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With