我应该如何实现具有某些(相同)类型的实例的自定义计数器?

时间:2017-12-12 08:28:51

标签: django

这是我的模特:

class MyModel(BaseModel):
    no = models.PositiveIntegerField()  # Human-friendly no -> counter that starts from 1 for every type
    type = models.ForeignKey(MyModelType)

我在模型的no信号中设置了pre_save字段的值:

@receiver(pre_save, sender=MyModel)
def mymodel_pre_save(sender, instance, *args, **kwargs):
    if not instance._state.adding:
        return

    obj_count = MyModel.objects.filter(type=instance.type).count()
    instance.no = obj_count + 1 

此代码的错误是它生成具有相同no个数字的不同对象。当多个用户同时创建对象时,可能会发生这种情况。

解决问题的最佳方法是什么?将分配移动到模型的.save()方法是否足以在高负载环境中使用?

解决方案

最终,我实施了@ moooeeeep的解决方案,但将逻辑从post_save移到了模型的save方法:

class MyModel(BaseModel):
    ...

    def save(self, *args, **kwargs):
        adding = self.pk is None

        with transaction.atomic():
            super().save(*args, **kwargs)

            if adding:
                # Compute and set `.no`
                obj_count = MyModel.objects.filter(type=self.type, id__lte=self.id).count()
                self.no = obj_count
                self.save(update_fields=['no'])

2 个答案:

答案 0 :(得分:0)

您可以使用select_for_update()方法获取db currence牵引问题的实例对象。但在高负载环境中,select_for_update方法将导致等待。

解决方案1:我建议你将这个计数更改代码从pre_save移动到post_save,如果是这样,你不需要加一个,只需将新计数放到无字段。

解决方案2:不要在数据库中存储类型计数no,只需从查询中获取它或将其缓存在redisDB中。

我的英语很差,抱歉。

答案 1 :(得分:0)

我可以想象你的问题是由竞争条件造成的:

  • 一个请求要求计数,
  • 另一个请求要求计数,
  • 一个请求插入一个带有count + 1的新项目,
  • 另一个请求会插入一个具有相同计数+ 1的新项目。

我能想象的最简单的方法是,在插入之前不要设置项目的数量,但之后。然后,通过计算id小于或等于相应对象的id的适当类型的项目数,计算该类型对象的计数。然后在post_save()处理程序中更新该值。

请注意,在计算项目后删除项目时,您的计数字段可能仍会出错。如果您要允许删除项目,则可能需要更新id大于您要删除的id的所有计数。

总之,从Django方面完全分配计数可能是一个坏主意。考虑使用数据库功能将计数分配为默认值(从广义上讲)计算 max(count),其中类型等于新插入项的类型或零加一个。并且不用担心要删除的物品。如果您想要准确的计数,请在需要时进行计数查询。