Quelle est la différence entre select related et prefetch related dans Django ORM?

Dans Django doc,

select_related() "suit" relations de clé étrangère, en sélectionnant des données d'objets connexes supplémentaires lorsqu'il exécute sa requête.

prefetch_related() fait une recherche distincte pour chaque relation, et le "rejoindre" en Python.

Qu'est-ce que cela signifie par "faire la jointure en python"? Quelqu'un peut-il illustrer avec un exemple?

Ma compréhension est que pour la relation de clé étrangère, utilisez select_related; et pour la relation M2M, utilisez prefetch_related. Est-ce correct?

148
demandé sur Paolo 2015-07-06 05:31:40

2 réponses

Votre compréhension est la plupart du temps correcte. Vous utilisez select_related lorsque l'objet que vous allez choisir est un objet unique, de sorte que OneToOneField ou ForeignKey. Vous utilisez prefetch_related quand vous allez obtenir un "ensemble" de choses, donc ManyToManyField s comme vous l'avez indiqué ou reverse ForeignKey s. juste pour clarifier ce que je veux dire par "reverse ForeignKey s" voici un exemple:

class ModelA(models.Model):
    pass

class ModelB(models.Model):
    a = ForeignKey(ModelA)

ModelB.objects.select_related('a').all() # Forward ForeignKey relationship
ModelA.objects.prefetch_related('modelb_set').all() # Reverse ForeignKey relationship

La différence est que select_related effectue une jointure SQL et récupère donc les résultats dans le cadre de la table à partir du serveur SQL. prefetch_related d'autre part exécute une autre requête et réduit donc les colonnes redondantes dans l'objet d'origine (ModelA dans l'exemple ci-dessus). Vous pouvez utiliser prefetch_related pour tout ce que vous pouvez utiliser select_related pour.

Les compromis sont que prefetch_related doit créer et envoyer une liste d'identifiants à sélectionner sur le serveur, cela peut prendre un certain temps. Je ne suis pas sûr s'il y a une bonne façon de le faire dans une transaction, mais je crois comprendre que Django envoie toujours une liste et dit SELECT ... Où pk dans (...,...,...) fondamentalement. Dans ce cas si les données pré-extraites sont rares (disons les objets D'État américains liés aux adresses des gens) cela peut être très bon, mais si c'est plus proche de one-to-one, cela peut gaspiller beaucoup de communications. En cas de doute, essayez les deux et voyez lequel fonctionne le mieux.

Tout ce qui est discuté ci-dessus concerne essentiellement les communications avec la base de données. Du côté de Python cependant prefetch_related A l'avantage supplémentaire qu'un seul objet est utilisé pour représenter chaque objet dans la base de données. Avec select_related dupliquer les objets seront créés en Python pour chaque objet "parent". Puisque les objets en Python ont une surcharge de mémoire décente, cela peut également être une considération.

251
répondu CrazyCasta 2017-08-16 16:01:39

Les deux méthodes atteignent le même but, pour renoncer aux requêtes db inutiles. Mais ils utilisent des approches différentes pour l'efficacité.

La seule raison d'utiliser l'une ou l'autre de ces méthodes est lorsqu'une seule grande requête est préférable à de nombreuses petites requêtes. Django utilise la requête large pour créer des modèles en mémoire de manière préventive plutôt que d'effectuer des requêtes à la demande sur la base de données.

select_related effectue une jointure avec chaque recherche, mais étend la sélection pour inclure les colonnes de toutes les jointures table. Cependant, cette approche a une mise en garde.

Jointures ont le potentiel de multiplier le nombre de lignes d'une requête. Lorsque vous effectuez une jointure sur une clé étrangère ou un champ un à un, le nombre de lignes n'augmente pas. Cependant, les jointures plusieurs à plusieurs n'ont pas cette garantie. Ainsi, Django limite select_related aux relations qui n'entraîneront pas de manière inattendue une jointure massive.

Le ,"join in python" pour prefetch_related est un peu plus inquiétant, alors il devrait être. Il crée une requête séparée pour chaque table à joindre. Il filtre chacune de ces tables avec une clause WHERE IN, comme:

SELECT "credential"."id",
       "credential"."uuid",
       "credential"."identity_id"
FROM   "credential"
WHERE  "credential"."identity_id" IN
    (84706, 48746, 871441, 84713, 76492, 84621, 51472);

Plutôt que d'effectuer une seule jointure avec potentiellement trop de lignes, chaque tableau est divisé en une requête distincte.

8
répondu cdosborn 2017-07-28 15:40:58