5

我有一个我正在尝试优化的 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 经验来找到它。任何指针将不胜感激!

4

3 回答 3

3

如果您需要一次处理超过 2 个级别,您可以考虑使用MPTT在数据库中存储树的不同方法

简而言之,它将数据添加到您的模型中,这些数据在更新期间会更新,并允许更有效的检索。

于 2012-10-10T10:44:29.400 回答
3

添加 arelated_name到外键,然后使用prefetch_related添加到 Django 1.4 的方法:

返回QuerySet将在单个批次中自动检索每个指定查找的相关对象的 a。

这与 的目的相似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')

所有相关子项将在单个查询中获取,并用于制作具有相关结果的预填充缓存的 QuerySet。然后在self.children.all() 调用中使用这些 QuerySet。

注意 1,与 QuerySets 一样,任何暗示不同数据库查询的后续链接方法都将忽略以前缓存的结果,并使用新的数据库查询检索数据。

注意 2,如果您用于iterator()运行查询,prefetch_related()调用将被忽略,因为这两个优化一起没有意义。

于 2012-10-10T07:36:39.380 回答
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 调试工具栏是您的朋友。

于 2012-10-10T07:49:21.963 回答