【问题标题】:Django: Maintaining a counter in a field (race condition)Django:在字段中维护一个计数器(竞争条件)
【发布时间】:2018-07-25 10:39:22
【问题描述】:

我有一个类似这个例子的模型

class Foo(models.Model):
    a = models.ForeignKey(...)
    number = models.IntegerField()

    @transaction.atomic
    def save(self, commit=True):
        if self.pk is None:            
            current_max = (
                Foo.objects
                .filter(a=self.a)
                .order_by('-number')
                .first()
            )
            current_max = 0 if current_max is None else current_max.number
            self.number = current_max + 1
        return super().save(commit)

这个想法是对于每个a,都会有一系列Foos,从1开始编号。

问题是,即使我们有@transaction.atomic,也存在竞争条件,因为 Django 期望的事务隔离级别将允许事务同时运行,即

A -> Get max -> 42
B -> Get max -> 42
A -> Set max + 1
A -> save
B -> Set max + 1
B -> save
Both will be 43

那我该如何解决呢?有没有办法自动设置计数器,这样我就不会在检索当前最大值和插入新值之间出现竞争条件?

这个问题是similar to this one,但不同的是,这个问题没有为我的具体示例提供答案

【问题讨论】:

    标签: django transactions race-condition


    【解决方案1】:

    查看select_for_update。如文档中所述:

    返回一个查询集,它将锁定行直到事务结束,在支持的数据库上生成一个 SELECT ... FOR UPDATE SQL 语句。

    另外,Haki Benata 在https://web.archive.org/web/20170707121253/https://medium.com/@hakibenita/how-to-manage-concurrency-in-django-models-b240fed4ee2 定义了两种方法,这可能是有趣的阅读

    最后,如果您需要锁定整个表,有一个 method 描述可以让您这样做。实际上,您创建了一个锁定上下文管理器,它获取一个完整的表锁并在退出时释放它

    【讨论】:

    • 这会锁定一个。但是这里我们需要锁定表,因为此时没有任何行映射到实例,并且聚合跨越了表。
    • 你最后的想法,从另一个答案中引用LockedAtomicTransaction 对我来说似乎是一个合适的解决方案。谢谢
    猜你喜欢
    • 2011-07-17
    • 2012-11-24
    • 2023-03-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多