如何避免在 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 问题