django中计数器的原子增量

时间:2009-10-21 05:49:09

标签: python django transactions race-condition

我正试图在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,这应该将函数包装在事务中并使增量原子化。但它不起作用,并且在计数器更新中存在竞争条件。如何使这些代码成为线程安全的?

6 个答案:

答案 0 :(得分:79)

New in Django 1.1

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

或使用an F expression

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

请记住指定要更新的字段, 或者您可能会在模型的其他可能字段上遇到竞争条件!

race condition associated with this approach上的主题已添加到官方文档中。

答案 1 :(得分:16)

在Django 1.4中有support for SELECT ... FOR UPDATE个子句,使用数据库锁来确保错误地同时访问数据。

答案 2 :(得分:10)

如果您在设置时不需要知道计数器的价值,那么最佳答案肯定是您最好的选择:

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

这告诉您的数据库将count的值加1,它可以很好地完成,而不会阻止其他操作。缺点是您无法知道您刚设置的count。如果两个线程同时命中了这个函数,它们都会看到相同的值,并且都会告诉数据库添加1.数据库最终会按预期添加2,但你不会知道哪一个先行。

如果您现在关心计数,可以使用Emil Stenstrom引用的select_for_update选项。这是看起来像:

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的更多信息,请参阅the docs

答案 3 :(得分:7)

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

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

这里的优点是,如果对象是在第一个语句中创建的,则不必进行任何进一步的更新。

答案 4 :(得分:5)

Django 1.7

from django.db.models import F

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

答案 5 :(得分:-3)

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

- 西