Django transaction.atomic () guarantees atomic READ + WRITE?

I need to make sure that an object that is being read from the database and written back cannot be changed in the meantime by another request / process.

Does transaction.atomic () provide this?

My tests don't tell me yet. If something is wrong with them, what would be the right way to achieve atomic READS and WRITES?


My example that I tested.

Put the Test class somewhere in your model. atomic_test.py and atomic_test2.py should be saved as control commands. Run python manage.py atomic_test first, then python manage.py atomic_test2. The second script is not blocked 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 
+8
source share
2 answers

Django transaction.atomic() is a subtle abstraction over database transaction tools. Therefore, its behavior really depends on the database level and depends on the type of database and its settings. So in order to really understand how this works, you need to read and understand the transaction documentation for your database. (For example, in PostgreSQL, the relevant documentation is transaction isolation and explicit locking ).

As for your specific test case, the desired behavior can be achieved using the select_for_update() method in the Django query set (if your database supports it). Sort of:

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 should block until the first ends, and see the new value of 60.

Other options include using SERIALIZABLE transaction isolation isolation or SERIALIZABLE transaction lock, although Django does not provide a convenient API for this purpose.

+11
source

It would be much better to update atomically in 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 like "UPDATE test_test SET value = value + 10 WHERE id = 1" )

+2
source

Source: https://habr.com/ru/post/978774/


All Articles