我正在尝试实现(我认为)一个非常简单的计数器数据模型:
class VisitorDayTypeCounter(models.Model): visitType = models.CharField(max_length=60) visitDate = models.DateField('Visit Date') counter = models.IntegerField()
当有人通过时,它将查找与visitType和visitDate匹配的行; 如果此行不存在,则将使用counter = 0创建.
然后我们增加计数器并保存.
我担心的是这个过程完全是一场竞赛.两个请求可以同时检查实体是否存在,并且它们都可以创建它.在读取计数器并保存结果之间,另一个请求可能会通过并递增(导致计数丢失).
到目前为止,我还没有找到一个很好的方法,无论是在Django文档还是在教程中(实际上,看起来教程在投票部分有竞争条件).
我该如何安全地做到这一点?
从Django 1.1开始,您可以使用ORM的F()表达式.
from django.db.models import F product = Product.objects.get(name='Venezuelan Beaver Cheese') product.number_sold = F('number_sold') + 1 product.save()
有关详细信息,请参阅文档:
https://docs.djangoproject.com/en/1.8/ref/models/instances/#updating-attributes-based-on-existing-fields
https://docs.djangoproject.com/en/1.8/ref/models/expressions/#django.db.models.F
如果您真的希望计数器准确,您可以使用事务,但所需的并发数量将在任何重大负载下真正拖动您的应用程序和数据库.相反,可以考虑使用更多的消息传递方式,只需将计数记录转储到每个访问的表中,您需要增加计数器.然后,当您希望总访问次数计入访问表时.您还可以拥有一个后台进程,该进程每天运行任意次数,以便对访问进行求和,然后将其存储在父表中.为了节省空间,它还将删除它总结的子访问表中的任何记录.如果您没有多个代理商争夺相同的资源(计数器),您将大大降低并发成本.
您可以使用http://code.djangoproject.com/ticket/2705中的补丁进行支持数据库级别锁定.
使用补丁,此代码将是原子的:
visitors = VisitorDayTypeCounter.objects.get(day=curday).for_update() visitors.counter += 1 visitors.save()
两个建议:
将unique_together添加到模型中,并将创建包装在异常处理程序中以捕获重复项:
class VisitorDayTypeCounter(models.Model): visitType = models.CharField(max_length=60) visitDate = models.DateField('Visit Date') counter = models.IntegerField() class Meta: unique_together = (('visitType', 'visitDate'))
在此之后,您可能会在计数器的更新中遇到轻微的竞争条件.如果你有足够的流量来关注它,我会建议查看更精细的数据库控制的事务.我不认为ORM直接支持锁定/同步.交易文档可在此处获得.