如何避免在 django 中选择 n+1?

Posted

技术标签:

【中文标题】如何避免在 django 中选择 n+1?【英文标题】:How to avoid n+1 select in django? 【发布时间】:2011-06-17 16:16:20 【问题描述】:

我有一个非常简单的数据模型,视频和 cmets 之间存在一对多关系

class Video(models.Model):
    url = models.URLField(unique=True)
    .....

class Comment(models.Model):
    title = models.CharField(max_length=128)
    video = models.ForeignKey('Video')
        .....

我想查询视频并获取整个对象图(包含所有 cmets 的视频)。查看 sql,我看到它做了两个选择,一个用于视频,一个用于评论。我该如何避免呢?我想加入并立即获取所有内容。

django 可以做到这一点吗?

【问题讨论】:

【参考方案1】:

For ForeignKey,可以使用selected_related():

Comment.objects.select_related('video').all()

它只会生成一个查询,为您收集评论以及视频。

对于更复杂的事情(例如 M2M),您需要一个外部应用程序(例如 unjoinify)进行优化,但它使用 SQL 查询然后将它们放回对象中。

如果您对此不满意(我是),您有一些选择:

django-queryset-transform:不是完整的解决方案,但有帮助 django-batch-select:大致是 select_related,适用于 M2M 和反向关系。

【讨论】:

我想做相反的事情,从视频中获取 cmets。我想查询视频但获取这些视频的所有 cmets 并生成如下 sql 语句: select * from video v join v.id=c.video_id where v.date>some_date.我们的想法是在一个查询中获取所有视频及其 cmets。 抱歉,我要生成的 sql 语句不正确。我想生成以下内容:select * from Videos v left join Comment c on v.id = c.id where v.date>some_date。基本上我想要所有的视频和他们的 cmets,甚至是没有 cmets 的视频。此示例 Comment.objects.filter(video__title__starts_with='The') .select_related('video').all() 不会获取没有 cmets 的视频。 这就是为什么我给你一个链接到其他工具的链接,比如 django-batch-select 可以为你做到这一点:“大概是一个 select_related 与 M2M 和 REVERSE RELATIONS 一起工作”。 M2M 关系有什么更新吗?这里指出的库已经 10 多年没有更新了...【参考方案2】:

您需要做的是在评论上使用 select_related。

假设您需要找到标题以“The”开头的所有视频以及与之相关的 cmets

comments = Comment.objects.filter(video__title__starts_with='The')
           .select_related('video').all()

这将加载该评论的所有 cmets 和相应的 Video 对象。您仍然需要在视频上进行旋转以迭代视频。使用 python itertools.groupby 函数在内存中进行旋转。

【讨论】:

【参考方案3】:

看看select related 是否按您期望的方式工作,它就是为此而生的。

【讨论】:

以上是关于如何避免在 django 中选择 n+1?的主要内容,如果未能解决你的问题,请参考以下文章

使用 prefetch_related 和聚合来避免 Django 数据库查询具有时间序列数据的模型的 n+1 问题

JPQL如何总结孩子的给定属性并获取这些孩子避免n + 1个选择?

如何在django中复制数据,我该如何避免?

如何避免在 django sorl 缩略图中拉伸图像

如何避免 Django 中的导入时数据库访问?

如何避免在 Django 中使用 context_processors.py 时在登录页面上请求用户?