64

我试图在 Django 中原子地增加一个简单的计数器。我的代码如下所示:

from models import Counter
from django.db import transaction

@transaction.commit_on_success
def increment_counter(name):
    counter = Counter.objects.get_or_create(name = name)[0]
    counter.count += 1
    counter.save()

如果我正确理解 Django,这应该将函数包装在事务中并使增量原子化。但它不起作用,并且计数器更新中存在竞争条件。如何使这段代码成为线程安全的?

4

6 回答 6

106

使用F 表达式

from django.db.models import F

要么在update()

Counter.objects.get_or_create(name=name)
Counter.objects.filter(name=name).update(count=F("count") + 1)

或在对象实例上:

counter, _ = Counter.objects.get_or_create(name=name)
counter.count = F("count") + 1
counter.save(update_fields=["count"])

请记住指定update_fields,否则您可能会在模型的其他字段上遇到竞争条件。

官方文档中添加了关于使用 F 表达式避免竞争条件的说明。

于 2009-10-21T06:43:45.020 回答
18

在 Django 1.4 中,支持 SELECT ... FOR UPDATE子句,使用数据库锁来确保没有数据被错误地并发访问。

于 2012-01-17T21:26:25.243 回答
16

如果您在设置时不需要知道计数器的值,那么上面的答案绝对是您最好的选择:

counter, _ = Counter.objects.get_or_create(name = name)
counter.count = F('count') + 1
counter.save()

这告诉您的数据库将 1 的值加 1 count,它可以在不阻塞其他操作的情况下做得很好。缺点是你无法知道count你刚刚设置了什么。如果两个线程同时访问这个函数,它们都会看到相同的值,并且都会告诉 db 加 1。db 最终会按预期加 2,但你不知道哪个先走。

如果您现在确实关心计数,可以使用select_for_updateEmil Stenstrom 引用的选项。看起来是这样的:

from models import Counter
from django.db import transaction

@transaction.atomic
def increment_counter(name):
    counter = (Counter.objects
               .select_for_update()
               .get_or_create(name=name)[0]
    counter.count += 1
    counter.save()

这会读取当前值并锁定匹配的行,直到事务结束。现在一次只有一名工人可以阅读。有关 select_for_update 的更多信息,请参阅文档

于 2017-09-29T19:14:38.197 回答
8

保持简单并以@Oduvan 的回答为基础:

counter, created = Counter.objects.get_or_create(name = name, 
                                                 defaults={'count':1})
if not created:
    counter.count = F('count') +1
    counter.save()

这样做的好处是,如果对象是在第一条语句中创建的,则无需进行任何进一步的更新。

于 2013-09-21T20:44:27.533 回答
6

姜戈 1.7

from django.db.models import F

counter, created = Counter.objects.get_or_create(name = name)
counter.count = F('count') +1
counter.save()
于 2015-01-30T03:53:05.723 回答
-2

或者,如果您只想要一个计数器而不是持久对象,您可以使用在 C 中实现的 itertools 计数器。GIL 将提供所需的安全性。

——赛

于 2010-08-26T07:33:45.590 回答