105 votes

Pourquoi la fonction prefetch_related() de django ne fonctionne-t-elle qu'avec all() et non avec filter() ?

Je suppose que j'ai ce modèle :

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()

Maintenant, si je veux regarder un sous-ensemble de photos dans un sous-ensemble d'albums efficacement. Je fais quelque chose comme ça :

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

Cela ne fait que deux requêtes, ce qui est ce que j'attends (une pour obtenir les albums, et ensuite une comme `SELECT * IN photos WHERE photoalbum_id IN ().

Tout est génial.

Mais si je fais ça :

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

Ensuite, il fait une tonne de requêtes avec WHERE format = 1 ! Est-ce que je fais quelque chose de mal ou est-ce que django n'est pas assez intelligent pour réaliser qu'il a déjà récupéré toutes les photos et peut les filtrer en python ? Je jure avoir lu quelque part dans la documentation qu'il est censé faire ça...

204voto

Alasdair Points 36535

Dans Django 1.6 et antérieur, il n'est pas possible d'éviter les requêtes supplémentaires. L'adresse prefetch_related met effectivement en cache les résultats de a.photoset.all() pour chaque album dans le queryset. Cependant, a.photoset.filter(format=1) est un ensemble de requêtes différent, vous générerez donc une requête supplémentaire pour chaque album.

Ceci est expliqué dans le prefetch_related docs. Le site filter(format=1) est équivalent à filter(spicy=True) .

Notez que vous pouvez réduire le nombre de requêtes en filtrant les photos en 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]

Dans Django 1.7, il existe une fonction Prefetch() qui vous permet de contrôler le comportement de 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

Pour plus d'exemples sur la façon d'utiliser le Prefetch voir le prefetch_related docs.

11voto

Ngure Nyaga Points 1645

Desde el docs :

...comme toujours avec les QuerySets, toutes les méthodes enchaînées suivantes qui impliquent une requête de base de données différente ignoreront les résultats précédemment mis en cache et récupéreront les données en utilisant une nouvelle requête de base de données. Donc, si vous écrivez ce qui suit :

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

...alors le fait que pizza.toppings.all() ait été préenregistré ne vous aidera pas - en fait, cela nuit aux performances, puisque vous avez effectué une requête de base de données que vous n'avez pas utilisée. Utilisez donc cette fonctionnalité avec prudence !

Dans votre cas, "a.photo_set.filter(format=1)" est traité comme une nouvelle requête.

En outre, "photo_set" est une recherche inversée, mise en œuvre par un gestionnaire totalement différent.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X