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

Django: bulk_create, bulk_update e update() con F per scritture veloci

2026-10-16

Importare 5.000 righe con un ciclo di create() o aggiornarle con save() è il secondo classico collo di bottiglia dopo le query N+1: ogni riga costa una query. Django offre tre strumenti per evitarlo. Qui li misuriamo su un esempio eseguibile.

Il modello

class Prodotto(models.Model):
    nome = models.CharField(max_length=50)
    prezzo = models.IntegerField(default=0)

Inserimenti

# lento: una INSERT per riga
for i in range(5000):
    Prodotto.objects.create(nome=f"p{i}", prezzo=i)

# veloce: INSERT a blocchi
Prodotto.objects.bulk_create(
    [Prodotto(nome=f"p{i}", prezzo=i) for i in range(5000)]
)

Aggiornamenti

# lento: una UPDATE per riga
for p in Prodotto.objects.all():
    p.prezzo += 1
    p.save()

# a blocchi, utile se il nuovo valore dipende da logica Python
ps = list(Prodotto.objects.all())
for p in ps:
    p.prezzo += 1
Prodotto.objects.bulk_update(ps, ["prezzo"], batch_size=500)

# una sola query, se il calcolo si esprime in SQL
Prodotto.objects.update(prezzo=models.F("prezzo") + 1)

Misure

Eseguite con Django 4.2 su SQLite in memoria, 5.000 righe (tempi indicativi, variano da run a run; il rapporto è ciò che conta):

Operazione Query Tempo
create() in ciclo 5000 ~2,9 s
create() in ciclo dentro transaction.atomic() 5002 ~1,7 s
bulk_create 13 ~0,2 s
save() in ciclo 5001 ~4,2 s
bulk_update, batch 500 19 ~1,6 s
update(F()) 1 ~0,006 s

Le 13 query di bulk_create non sono un errore: SQLite accetta al massimo 999 parametri per query, quindi Django divide in blocchi da circa 499 righe (2 campi ciascuna). Su PostgreSQL i blocchi sono molto più grandi.

Cosa ricordare

Il codice completo di misura è nello script di verifica del progetto; ricostruiscilo con il modello sopra e CaptureQueriesContext per contare le query sul tuo caso reale.