Django:维护字段中的计数器(竞争条件)
Django: Maintaining a counter in a field (race condition)
我有一个类似于这个例子的模型
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
,都会有一系列 Foo
,从 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,但差异很大,以至于那个问题没有为我的具体示例提供答案
查看 select_for_update
。如文档中所述:
Returns a queryset that will lock rows until the end of the transaction, generating a SELECT ... FOR UPDATE SQL statement on supported databases.
另外,Haki Benata 在 https://web.archive.org/web/20170707121253/https://medium.com/@hakibenita/how-to-manage-concurrency-in-django-models-b240fed4ee2 定义了两种方法,可能值得一读
最后,如果您需要锁定整个 table,有一个 method 描述可以让您这样做。实际上,您创建了一个锁定上下文管理器,它获取一个完整的 table 锁并在退出时释放它
我有一个类似于这个例子的模型
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
,都会有一系列 Foo
,从 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,但差异很大,以至于那个问题没有为我的具体示例提供答案
查看 select_for_update
。如文档中所述:
Returns a queryset that will lock rows until the end of the transaction, generating a SELECT ... FOR UPDATE SQL statement on supported databases.
另外,Haki Benata 在 https://web.archive.org/web/20170707121253/https://medium.com/@hakibenita/how-to-manage-concurrency-in-django-models-b240fed4ee2 定义了两种方法,可能值得一读
最后,如果您需要锁定整个 table,有一个 method 描述可以让您这样做。实际上,您创建了一个锁定上下文管理器,它获取一个完整的 table 锁并在退出时释放它