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