具有较少查询的父对象及其子对象列表

时间:2012-10-10 07:23:50

标签: python django query-optimization django-queryset

我有一个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经验来找到它。任何指针都会非常感激!

3 个答案:

答案 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支持的关系。它也支持   预取GenericRelationGenericForeignKey

  •   
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调试工具栏就是你的朋友。