我有一个Django视图,我正在尝试优化。它显示页面上的父对象列表及其子对象。子模型将外键返回到父项,因此select_related
似乎不适用。
class Parent(models.Model):
name = models.CharField(max_length=31)
class Child(models.Model):
name = models.CharField(max_length=31)
parent = models.ForeignKey(Parent)
一个简单的实现使用 n + 1 查询,其中 n 是父对象的数量,即。一个查询用于获取父列表,然后一个查询用于获取每个父项的子项。
我写了一个视图,它在两个查询中完成了工作 - 一个用于获取父对象,另一个用于获取相关的子项,然后是一些Python(我太尴尬了,无法在此处发布)来完成所有操作再次回到一起。
一旦我发现自己导入标准库的collections
模块,我意识到我可能做错了。可能有一个更简单的方法,但我缺乏Django经验来找到它。任何指针都会非常感激!
答案 0 :(得分:3)
向外键添加related_name
,然后使用添加到Django 1.4的prefetch_related
方法:
返回一个
QuerySet
,它将自动检索到一个 批处理,每个指定查找的相关对象。这与
select_related
的目的相似,两者都是。{ 旨在阻止由此引起的数据库查询的泛滥 访问相关对象,但策略完全不同:
- 另一方面,
select_related
通过创建SQL连接并包含字段来工作SELECT
语句中的相关对象。为此原因,select_related
获取相同数据库查询中的相关对象。 但是,要避免由此产生的更大的结果集 加入“很多”关系,select_related
仅限于此 单值关系 - 外键和一对一。
prefetch_related
对每个进行单独查找 关系,并在Python中“加入”。这允许它 预取多对多和多对一对象,这是无法完成的 使用select_related
,除了外键和一对一select_related
支持的关系。它也支持 预取GenericRelation
和GenericForeignKey
。
class Parent(models.Model):
name = models.CharField(max_length=31)
class Child(models.Model):
name = models.CharField(max_length=31)
parent = models.ForeignKey(Parent, related_name='children')
>>> Parent.objects.all().prefetch_related('children')
所有相关的孩子都将在一个查询中获取并使用
使QuerySets具有相关的预填充缓存
结果。然后在self.children.all()
中使用这些QuerySet
调用
注1 ,与QuerySets一样,任何暗示不同数据库查询的后续链接方法都会先前忽略 缓存结果,并使用新的数据库查询检索数据。
注意2 如果您使用
iterator()
来运行查询,那么prefetch_related()
次调用将被忽略 优化没有任何意义。
答案 1 :(得分:3)
如果您需要同时使用超过2个级别,可以考虑使用MPTT
在db中存储树的不同方法简而言之,它会为您的模型添加数据,这些数据会在更新期间更新,并且可以更有效地检索。
答案 2 :(得分:0)
实际上,select_related是你要找的。 select_related创建一个JOIN,以便在一个语句中获取所需的所有数据。 prefetch_related一次运行所有查询,然后缓存它们。
这里的诀窍就是“加入”你唯一需要的东西,以减少连接的性能损失。“你绝对需要的”是很长的说法您应该只预先选择稍后将在视图或模板中阅读的字段。这里有很好的文档:https://docs.djangoproject.com/en/1.4/ref/models/querysets/#select-related
这是我的一个模特的片段,我遇到了类似的问题:
return QuantitativeResult.objects.select_related(
'enrollment__subscription__configuration__analyte',
'enrollment__subscription__unit',
'enrollment__subscription__configuration__analyte__unit',
'enrollment__subscription__lab',
'enrollment__subscription__instrument_model'
'enrollment__subscription__instrument',
'enrollment__subscription__configuration__method',
'enrollment__subscription__configuration__reagent',
'enrollment__subscription__configuration__reagent__manufacturer',
'enrollment__subscription__instrument_model__instrument__manufacturer'
).filter(<snip, snip - stuff edited out>)
在这个病态案例中,我从700多个查询下降到只有一个。当涉及到这类问题时,django调试工具栏就是你的朋友。