web-dev-qa-db-fra.com

Quelle est la différence entre select_related et prefetch_related dans Django ORM?

Dans Django doc,

select_related() "suit" les relations de clé étrangère, sélectionnant des données d'objet associé supplémentaires lors de l'exécution de la requête.

prefetch_related() effectue une recherche distincte pour chaque relation et effectue la "jointure" en Python.

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

D'après ce que je comprends, pour la relation de clé étrangère, utilisez select_related; et pour la relation M2M, utilisez prefetch_related. Est-ce correct?

225
NeoWang

Votre compréhension est généralement correcte. Vous utilisez select_related lorsque l'objet que vous allez sélectionner est un objet unique, donc OneToOneField ou un ForeignKey. Vous utilisez prefetch_related pour obtenir un "ensemble" de choses, donc ManyToManyFields comme vous l'avez indiqué ou inversez ForeignKeys. Juste pour clarifier ce que je veux dire par "reverse ForeignKeys", 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 de l'objet d'origine (ModelA dans l'exemple ci-dessus). Vous pouvez utiliser prefetch_related pour tout ce que vous pouvez utiliser select_related.

Les compromis sont que prefetch_related doit créer et envoyer une liste d'ID à sélectionner de nouveau sur le serveur, cela peut prendre un certain temps. Je ne sais pas s'il existe un bon moyen de faire cela dans une transaction, mais je crois comprendre que Django envoie toujours une liste et dit SELECT ... WHERE pk IN (..., .. .,...) fondamentalement. Dans ce cas, si les données pré-extraites sont peu nombreuses (par exemple, des objets d’État américains liés à des adresses de personnes), cela peut être très utile, mais si elles sont plus proches de l’un-à-un, cela risque de gaspiller beaucoup de communications. En cas de doute, essayez les deux et voyez lequel donne de meilleurs résultats.

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

345
CrazyCasta

Les deux méthodes ont le même objectif, éviter les requêtes inutiles de base de données. 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 volumineuse pour créer des modèles en mémoire à titre préventif plutôt que d'effectuer des requêtes à la demande sur la base de données.

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

Les jointures peuvent potentiellement 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'augmentera pas. Cependant, les jointures plusieurs à plusieurs ne bénéficient pas de cette garantie. Ainsi, Django restreint select_related aux relations qui n'aboutiront pas de manière inattendue à une jointure massive.

Le "rejoindre en python" pour prefetch_related est un peu plus alarmant qu'il ne devrait l'être. Il crée une requête distincte pour chaque table à joindre. Il filtre chacune de ces tables avec une clause WHERE IN, comme ceci:

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 table est scindée en une requête distincte.

15
cdosborn