我的模型中有以下内容:
class Tag(models.Model):
name = models.CharField(max_length=255)
type = models.CharField(max_length=1)
person = models.ForeignKey(People, on_delete=models.CASCADE)
class People(models.Model):
name = models.CharField(max_length=255)
@cached_property
def tags(self):
return Tag.objects.filter(person=self, type="A")
我希望当我这样做时:
person = People.objects.get(pk=1)
tags = person.tags
这将导致1 db查询-只从数据库中获取人。然而,它会持续导致2个查询-标签表被一致地查询,即使这是应该缓存的。什么会导致这种情况?我没有正确使用cached_property吗?
为了说明这种情况,对模型进行了简化。
2条答案
按热度按时间vaqhlq811#
修饰的
tags()
方法返回一个尚未计算的 queryset。(在Django的文档中阅读更多关于何时计算查询集的信息)。要缓存查询的结果,你必须首先强制查询集计算为一个对象列表:5f0d552i2#
如果不查看多次实际调用缓存属性的代码,很难找出问题所在。但是,从您描述问题的方式来看,
cached_property
似乎是正确的方法,应该可以工作。我猜可能是对它的工作原理有一些误解。缓存属性的一个示例用例是:
但是,如果你这样做:
for循环每次迭代的第一次调用
person.tags
执行一个查询。这是因为tags
属性的结果是每个示例缓存的。如果您想在遍历people对象时提前缓存所需的所有标记,根据您的用例,有几种方法。
手动方法
聚合方式的单查询
对于这种方法,您需要确保您的外键具有相关的名称,以便能够进行“反向”查询:
指定
related_name
并不是严格要求的,因为Django给出了一个默认的相关名称,但我不记得这个名称是如何构建的,所以我总是明确地给出它。不要忘记删除
tags()
方法,因为名称会与相关名称“tags”冲突。注意,使用这种方法,
person.tags_names
将是一个字符串形式的标记名称列表,而不是Tag对象列表。使用annotate()有一些很棘手的方法来检索Tag对象,或者至少是多个字段,但我认为这超出了这个问题的范围。另外请注意,这只适用于PostgreSQL。
Django的内置方式:prefetch_related()
Django在QuerySet对象上提供了prefetch_related()方法。它被特别设计为手动方法的捷径。这种方法需要使用上面提到的外键
related_name
。注意,如果不需要按类型过滤标记,可以简单地执行
People.objects.prefetch_related('tags')
。