【问题标题】:Django transaction.atomic() guarantees atomic READ + WRITE?Django transaction.atomic() 保证原子读+写?
【发布时间】:2015-01-25 14:51:38
【问题描述】:

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

transaction.atomic() 能保证吗?

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


我测试过的例子。

Test 类放在模型中的某个位置。 atomic_test.pyatomic_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

【问题讨论】:

    标签: python django concurrency transactions atomic


    【解决方案1】:

    Django 的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 来执行这些操作。

    【讨论】:

    • 谢谢,选择select_for_update。到目前为止看起来不错。最终值应为 40 (50+10-20)。
    【解决方案2】:

    使用 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)

    【讨论】:

    • 你没有回答问题
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-09
    • 2016-11-04
    • 1970-01-01
    • 2014-03-27
    相关资源
    最近更新 更多