Чому prefetch_related () django працює лише з усіма (), а не фільтрує ()?


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? Клянусь, я десь у документації читав, що це повинно робити ...


можливий дублікат фільтра на prefetch_related у Django
akaihola

Відповіді:


167

У Django 1.6 та попередніх версіях неможливо уникнути зайвих запитів. prefetch_relatedВиклик ефективно кешируєт результати a.photoset.all()для кожного альбому в QuerySet. Однак 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

З документів :

... як завжди з QuerySets, будь-які наступні ланцюгові методи, що передбачають інший запит до бази даних, ігноруватимуть раніше кешовані результати та отримуватимуть дані за допомогою нового запиту до бази даних. Отже, якщо ви напишете наступне:

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'). Але порядок має значення, як ви вже пояснили.
Risadinha,

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.