djangoapp.it – Guide, trucchi e tutorial su Django in italiano

Django: eliminare le query N+1 con select_related e prefetch_related

2026-10-14

Il problema delle query N+1 è la causa più comune di pagine Django lente: una query carica N oggetti, poi ne parte una in più per ciascuno per leggere una relazione. Con 10 articoli sono 11 query; con 1000, 1001.

Un modello di esempio

class Autore(models.Model):
    nome = models.CharField(max_length=50)

class Tag(models.Model):
    nome = models.CharField(max_length=30)

class Articolo(models.Model):
    titolo = models.CharField(max_length=100)
    autore = models.ForeignKey(Autore, on_delete=models.CASCADE)
    tags = models.ManyToManyField(Tag)

Il codice che sembra innocuo

for a in Articolo.objects.all():
    print(a.autore.nome)   # una query per ogni articolo

Correzione 1: select_related per ForeignKey e OneToOne

Esegue una JOIN SQL e carica l'autore insieme all'articolo:

for a in Articolo.objects.select_related("autore"):
    print(a.autore.nome)   # nessuna query aggiuntiva

Correzione 2: prefetch_related per ManyToMany e relazioni inverse

Fa una seconda query sola per tutte le relazioni e le unisce in Python:

qs = Articolo.objects.select_related("autore").prefetch_related("tags")
for a in qs:
    print([t.nome for t in a.tags.all()])

Attenzione: a.tags.filter(...) o a.tags.order_by(...) ignorano il prefetch e rilanciano la query. Per filtrare usa Prefetch("tags", queryset=Tag.objects.filter(...)).

Numeri misurati

Con 10 articoli, 10 autori e 3 tag (SQLite in memoria, Django 4.2, conteggio con CaptureQueriesContext):

Caso Query
Ciclo naive su autore 11
select_related("autore") 1
tags.all() senza prefetch 11
select_related + prefetch_related("tags") 2

Lo script completo e riproducibile è scaricabile dal repository del sito (verifica/n_plus_uno.py).

Come accorgersene

Regola pratica: select_related per relazioni "verso uno", prefetch_related per relazioni "verso molti".