为什么Django的prefetch_related()仅适用于all()而不适用于filter()?


89

假设我有这个模型:

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中过滤它们?我发誓我在文档中的某个地方读到了应该做的...


Answers:


166

在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文档。


8

文档

...与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')。但正如您所解释的,顺序很重要。
里沙迪尼亚

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.