【问题标题】:Django Transactions, select_for_update, concurrent modification?Django Transactions,select_for_update,并发修改?
【发布时间】:2023-03-24 20:50:01
【问题描述】:

我有一堆视图,我在执行之前检查某些条件是否为真。首先执行哪个视图并不重要,但我担心一个用户使用的视图会在另一个视图中检查条件后更改数据。非常感谢您对此的任何帮助。

我正在使用设置 ATOMIC_REQUESTS = True 的 2 勺建议,将每个请求包装在事务中。我的网站流量相对较低,所以我还不关心性能。也使用 postgres。

def feed_dog(self, dog_id):
    dog = get_objects_or_404(Dog, pk=dog_id)
    if not dog.removed and not dog.fed_today:    # line 3
        # do additional checks
        dog.fed_today = True    # line 5
        dog.save()
        # also modify other related objects
        treat = Treats.objects.last()
        treat.dogs_being_fed_with_this_treat.add(dog)
        treat.save()

def remove_dog_from_feed_list(self, dog_id):
    dog = get_objects_or_404(Dog, pk=dog_id)
    if not dog.fed_today           # line 12
    dog.removed = True
    dog.save()
    treat.dogs_being_fed_with_this_treat.remove(dog)
    treat.save()

所以我担心有人调用了 feed_dog 视图,然后在检查了第 3 行之后但在第 5 行开始之前,另一个用户调用了 remove_dog 函数。由于当用户 2 调用 remove_dog 时 dog.fed_today 仍然为 False,第 12 行为 True,并且 remove_dog 视图继续执行 dog.removed = True。同时,feed_dog 视图继续并设置 dog.fed_today=True,将狗添加到 Treat 数组等。所以我有不一致的状态 dog.removed = True 和 dog.fed_today = True。

基本上我需要它,以便 Dog 模型上的某些属性不能同时为 True,例如 dog.fed_today =True 和 dog.removed=True。我也有这个相关的模型 Treat,我不希望 dog 对象被删除 = True 并且也在treat.dogs_being_fed 数组中。

问:这是一个有效的担忧吗?既然事务提供了隔离,这是否意味着其中一个视图在另一个视图之前或之后访问数据(它们看不到半完成状态的数据)?

问:增加 Django/Postgres 与 Read Committed 的隔离属性是否有帮助?

问:对 Dog 模型本身进行约束是否会有所帮助(例如使用 def clean 方法表示 Dog 不能同时删除=True 和 fed_today = True?)。我主要检查视图中的内容。这会反映在交易中吗?我是否也可以创建一个反映多个模型中的属性的约束,例如不能有 dog.removed=True 并且让那只狗在 Treats.dogs_being_fed 数组中?

问:select_for_update 的目的是什么,它在这里有帮助吗?如果事务已经应该提供隔离,那么 select_for_update 的目的是什么?

感谢任何帮助!

【问题讨论】:

    标签: django transactions django-views


    【解决方案1】:

    除非您了解数据库隔离级别,否则您真的无法有效地使用事务。如果您使用的是 PostgreSQL,请查看their documentation on the subject

    回答您的具体问题:

    这是一个有效的担忧吗?

    当然。默认的READ COMMITTED 隔离级别不会保护数据库免受上述代码的影响。

    既然事务提供了隔离,这是否意味着其中一个视图在另一个视图之前或之后访问数据(他们看不到半完成状态的数据)?

    没有。不过,这大概就是SERIALIZABLE 隔离级别的含义。

    READ COMMITTED增加Django / Postgres的隔离属性有帮助吗?

    是的,但是您通常不需要更严格的隔离级别,而且由于使用它们会降低性能,因此重新考虑您的数据库访问模式通常更有意义。

    Dog 模型本身进行约束是否有帮助(例如拥有clean() 方法)?

    没有。 Django 的验证方法应用于查询数据库后创建的 Python 对象。它们无助于防止由竞争条件引起的数据损坏。 (不过,他们可能会在事后检测它。)

    如果事务已经应该提供隔离,那么select_for_update() 的目的是什么?

    select_for_update() 执行SELECT 但锁定匹配的行,以便它们不能在其他事务中同时修改。如前所述,这不是事务隔离级别的作用。

    这里有帮助吗?

    是的!这是解决问题的最简单方法,因为您选择(并且可以锁定)Dog 表中的单行。如果两个函数都使用

    dog = Dog.objects.filter(pk=dog_id).select_for_update().get()
    

    然后他们都会尝试锁定有问题的行。如果尝试并发修改,第二个将等到第一个事务结束后再继续。

    【讨论】:

    • 谢谢,非常感谢。关于 select_for_update 的几个问题: 1)这个 nowait=False 设置的目的是什么;我什么时候想让这成为现实?我不是一直想要 nowait=False 以便其他视图必须等待视图完成吗? 2)如果我有其他只访问 Dog.objects.all() 的视图,喜欢显示所有 Dog 对象但不修改任何 Dog 对象,这些视图是否也必须等到我的视图之一在完成时使用 select_for_update?
    • 另外,3)3) 你是否知道我可以通过 python 单元测试或 Selenium 测试来测试并发访问?就像我如何进行 selenium 测试,其中两个浏览器尝试同时修改代码,并且我可以测试 Dog 对象在两个视图运行后不会最终处于不兼容状态?
    • @user3048472: 1) nowait 确定 Django 和数据库在发生冲突时如何反应。如果nowait=False(通常情况下,可能是您想要的),该进程将阻塞并等待冲突完成后再继续。如果nowait=True,将引发DatabaseError,您必须决定如何继续(例如放弃、重试等)。 2) 不,他们不会等的。但是事务隔离将使他们看不到处于不一致状态的对象。 3) 如果您遇到问题,您应该发布一个单独的问题。
    • 再次感谢,我会搜索 3) 看看能否找到答案。我发布了一个关于在同一查询中使用 select_for_update 和 select_related 的快速问题stackoverflow.com/questions/36253793/…)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-08-05
    • 2019-02-26
    • 1970-01-01
    • 2021-12-20
    • 2012-05-28
    • 2017-09-16
    相关资源
    最近更新 更多