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?
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 ManyToManyField
s comme vous l'avez indiqué ou inversez 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 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.
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.