Django transaction.atomic()保证原子READ + WRITE?

时间:2014-11-27 04:58:28

标签: python django concurrency transactions atomic

我需要确保从数据库中读取并写回的对象在此期间不能被另一个请求/进程修改。

transaction.atomic()是否保证?

到目前为止我的测试告诉我没有。如果它们没有什么问题,那么实现原子读取和写入的正确方法是什么?


我测试过的例子。

Test 类放在模型中的某个位置。 atomic_test.py atomic_test2.py 应保存为管理命令。首先运行 python manage.py atomic_test ,然后运行 python manage.py atomic_test2 。第二个脚本不会阻止,其更改将丢失。

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

2 个答案:

答案 0 :(得分:12)

Django' s transaction.atomic()是对数据库事务工具的精简抽象。所以它的行为实际上取决于数据库层,并且特定于数据库的类型及其设置。因此,要真正了解其工作原理,您需要阅读并理解数据库的事务文档。 (例如,在PostgreSQL中,相关文档是Transaction IsolationExplicit Locking)。

对于您的特定测试用例,可以通过在Django查询集上使用select_for_update()方法来实现所需的行为(如果您的数据库支持它)。类似的东西:

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()
atomic_test2.py

中的

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

第二个应该阻止,直到第一个完成,并看到新值60。

其他选项包括使用SERIALIZABLE事务隔离级别或使用行锁定,但Django没有提供方便的API来执行这些操作。

答案 1 :(得分:3)

使用F函数在数据库上进行原子更新会好得多:

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

(这会生成类似"UPDATE test_test SET value = value + 10 WHERE id = 1")的SQL