如何减少Django模型has_relation方法中的查询?

7 投票
1 回答
827 浏览
提问于 2025-04-16 18:47

这里有两个Django模型的例子。特别注意一下has_pet这个方法。

class Person(models.Model):
    name = models.CharField(max_length=255)

    def has_pet(self):
        return bool(self.pets.all().only('id'))

class Pet(models.Model):
    name = models.CharField(max_length=255)
    owner = models.ForeignKey(Person, blank=True, null=True, related_name="pets")

这里的问题是,has_pet方法每次都会生成一个查询。如果你这样做:

p = Person.objects.get(id=1)
if p.has_pet():
    ...

那么实际上你只是为了检查一个人是否有宠物而多做了一个查询。如果你需要检查很多人,这就会变成一个大问题。如果在模板中这样使用,也会生成查询。

{% for person in persons %}
    {% if person.has_pet %}
        {{ person.name }} owns a pet
    {% else %}
        {{ person.name }} is petless
    {% endif %}
{% endfor %}

这个例子在渲染模板的时候,实际上会为每一个在persons查询集中的人额外执行一个查询。

有没有办法只用一个查询,或者至少每个人少做一个额外的查询呢?也许可以换个设计方式,彻底避免这个问题。

我想到了给Person添加一个BooleanField(布尔字段),并在每次保存或删除宠物时更新这个字段。这样做真的合适吗?

另外,我已经正确设置了memcached,所以那些查询只有在结果没有被缓存时才会发生。我希望能从根本上减少查询,以实现更好的优化。

1 个回答

4

如果你想要获取所有有宠物的人的列表,可以通过一个查询就做到:

Person.objects.exclude(pets=None)

听起来你想要遍历一份包含所有人的列表,使用注解可能是个不错的选择:

for person in Person.objects.annotate(has_pet=Count('pets')):
     if person.has_pet: # if has_pet is > 0 this is True, no extra query

如果Django有一个叫Exists的聚合函数就好了,但实际上没有,我也不知道添加一个会有多难。当然,你应该先进行性能分析,看看这个方法是否适合你。

就我个人而言,我可能会在模型中直接存一个has_pets的布尔值,这可能是最有效的做法。

撰写回答