为啥 django 的 prefetch_related() 只适用于 all() 而不是 filter()?

Posted

技术标签:

【中文标题】为啥 django 的 prefetch_related() 只适用于 all() 而不是 filter()?【英文标题】:Why does django's prefetch_related() only work with all() and not filter()?为什么 django 的 prefetch_related() 只适用于 all() 而不是 filter()? 【发布时间】:2012-10-10 01:10:46 【问题描述】:

假设我有这个模型:

class PhotoAlbum(models.Model):
    title = models.CharField(max_length=128)
    author = models.CharField(max_length=128)

class Photo(models.Model):
    album = models.ForeignKey('PhotoAlbum')
    format = models.IntegerField()

现在,如果我想有效地查看相册子集中的照片子集。我这样做是这样的:

someAlbums = PhotoAlbum.objects.filter(author="Davey Jones").prefetch_related("photo_set")
for a in someAlbums:
    somePhotos = a.photo_set.all()

这仅执行两个查询,这是我所期望的(一个获取相册,然后一个类似 `SELECT * IN photos WHERE photoalbum_id IN ()。

一切都很棒。

但如果我这样做:

someAlbums = PhotoAlbum.objects.filter(author="Davey Jones").prefetch_related("photo_set")
for a in someAlbums:
    somePhotos = a.photo_set.filter(format=1)

然后它会使用WHERE format = 1 进行大量查询!我做错了什么还是 django 不够聪明,无法意识到它已经获取了所有照片并可以在 python 中过滤它们?我发誓我在文档的某个地方读到它应该这样做......

【问题讨论】:

Filter on prefetch_related in Django的可能重复 【参考方案1】:

在 Django 1.6 及更早版本中,无法避免额外的查询。 prefetch_related 调用有效地缓存了查询集中每个专辑的a.photoset.all() 的结果。但是,a.photoset.filter(format=1) 是一个不同的查询集,因此您将为每个专辑生成一个额外的查询。

prefetch_related 文档中对此进行了解释。 filter(format=1) 等价于 filter(spicy=True)

请注意,您可以通过在 python 中过滤照片来减少数量或查询:

someAlbums = PhotoAlbum.objects.filter(author="Davey Jones").prefetch_related("photo_set")
for a in someAlbums:
    somePhotos = [p for p in a.photo_set.all() if p.format == 1]

在 Django 1.7 中,有一个 Prefetch() 对象允许您控制 prefetch_related 的行为。

from django.db.models import Prefetch

someAlbums = PhotoAlbum.objects.filter(author="Davey Jones").prefetch_related(
    Prefetch(
        "photo_set",
        queryset=Photo.objects.filter(format=1),
        to_attr="some_photos"
    )
)
for a in someAlbums:
    somePhotos = a.some_photos

有关如何使用Prefetch 对象的更多示例,请参阅prefetch_related 文档。

【讨论】:

真的很有帮助【参考方案2】:

来自docs:

...与 QuerySet 一样,任何暗示不同数据库查询的后续链接方法都将忽略先前缓存的结果,并使用新的数据库查询检索数据。因此,如果您编写以下内容:

pizzas = Pizza.objects.prefetch_related('toppings') [list(pizza.toppings.filter(spicy=True)) for pizza in pizzas]

...那么,pizza.toppings.all() 已经被预取的事实对你没有帮助——事实上它会损害性能,因为你已经完成了一个你没有使用过的数据库查询。所以请谨慎使用此功能!

在您的情况下,“a.photo_set.filter(format=1)”被视为新查询。

此外,“photo_set”是一种反向查找 - 完全通过不同的管理器实现。

【讨论】:

photo_set 也可以使用.prefetch_related('photo_set') 预取。但正如您所解释的,顺序很重要。【参考方案3】:

如果你想与 filter() 一起使用,可以使用select_related

results = Geography.objects.filter(state__pk = 1).select_related('country')
results.query

更多:https://docs.djangoproject.com/en/3.1/ref/models/querysets/#select-related

【讨论】:

以上是关于为啥 django 的 prefetch_related() 只适用于 all() 而不是 filter()?的主要内容,如果未能解决你的问题,请参考以下文章

django为啥不能实现ajax

为啥我的“多对多” Django 模型中的 Django 的“add()”方法不采用?

Django 1.7 迁移不会重新创建删除的表,为啥?

Django:为啥有些模型字段会相互冲突?

为啥 Django 给我一个 404 错误

为啥这个 Django 日志记录不起作用?