简体   繁体   中英

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

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'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.

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" )

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM