← Nicola VitraniScrivimi

Note dal lavoro · Backend

Query N+1 in Laravel: come riconoscerle e toglierle

Backend · 4 min di lettura

Se una pagina Laravel diventa lenta man mano che i dati crescono, la prima cosa da controllare sono le query N+1: una query per caricare un elenco e poi una in più per ogni elemento, per recuperare una relazione. Con 20 righe sono 21 query, con 500 sono 501.

Come si presenta

// 1 query per gli ordini, poi 1 per ogni cliente
$orders = Order::all();

foreach ($orders as $order) {
    echo $order->customer->name;
}

Il codice è corretto e funziona, ed è proprio per questo che passa inosservato: il costo si vede solo con molti dati.

Come riconoscerle

  • Debugbar o Telescope mostrano il numero di query per richiesta e quelle duplicate.
  • DB::listen permette di registrare ogni query in sviluppo.
  • Dal Laravel 8.43 puoi far fallire i lazy load accidentali: Model::preventLazyLoading(! app()->isProduction()); nel metodo boot del AppServiceProvider. In sviluppo ricevi un’eccezione appena una relazione viene caricata in modo pigro, in produzione l’app non si rompe.

Come risolverle

// 2 query in totale
$orders = Order::with('customer')->get();

// solo le colonne che servono (includi la chiave esterna)
$orders = Order::with('customer:id,name')->get();

// un conteggio senza caricare le righe
$orders = Order::withCount('items')->get();
  • with() carica le relazioni insieme alla query principale, in una query aggiuntiva per relazione.
  • withCount() e simili calcolano conteggi e somme direttamente nel database.
  • load() e loadMissing() fanno lo stesso su una collezione già caricata.

Attenzione ai due estremi

L’eager loading carica tutto ciò che chiedi: con liste lunghe e relazioni pesanti rischi di riempire la memoria. In quel caso paginare, limitare le colonne o elaborare a blocchi (chunk, lazy). Il contrario non va meglio: caricare relazioni che la vista non usa è lavoro sprecato.

Una regola pratica

Ogni volta che un ciclo tocca $modello->relazione, chiediti dove viene caricata. Abilita preventLazyLoading in sviluppo e il problema smette di essere una sorpresa in produzione.