Почему django prefetch_related() работает только со всеми(), а не с фильтром()?
Предположим, что у меня есть эта модель:
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? Клянусь, я где-то читал в документации, что он должен это делать...
Ответы
Ответ 1
В Django 1.6 и ранее невозможно избежать дополнительных запросов. Вызов prefetch_related
эффективно кэширует результаты a.photoset.all()
для каждого альбома в наборе запросов. Однако a.photoset.filter(format=1)
- это другой запрос, поэтому вы создадите дополнительный запрос для каждого альбома.
Это объясняется в prefetch_related
docs. Значение 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.photoset.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
docs.
Ответ 2
Из docs:
... как всегда с QuerySets, любые последующие цепные методы, которые подразумевают другой запрос к базе данных, будут игнорировать ранее полученные в кэше результаты и извлекать данные с использованием нового запроса к базе данных. Итак, если вы пишете следующее:
pizzas = Pizza.objects.prefetch_related ('toppings') [list (pizza.toppings.filter(spicy = True)) для пиццы в пицце]
... то тот факт, что pizza.toppings.all() был предварительно запрограммирован, вам не поможет - на самом деле это повредит производительность, поскольку вы сделали запрос к базе данных, который вы не использовали. Поэтому используйте эту функцию с осторожностью!
В вашем случае "a.photo_set.filter(format = 1)" обрабатывается как новый запрос.
Кроме того, "photo_set" - это обратный поиск, реализованный через другого менеджера.