Non è il tuo database a essere lento, è la tua query: le basi dell'ottimizzazione MySQL in Laravel che ogni sviluppatore dovrebbe conoscere
Ogni sviluppatore Laravel ci è passato. Una pagina che si caricava in 200 ms improvvisamente impiega 3 secondi. Il primo istinto è incolpare il database: «MySQL è lento», «ci serve un server più potente», «passiamo a PostgreSQL». Ma ecco la verità scomoda — il tuo database non è quasi mai il problema. Sono le tue query.
MySQL è uno dei software più collaudati che esistano. Può gestire milioni di righe e migliaia di connessioni simultanee senza battere ciglio. Quello che non può fare è correggere magicamente query scritte male, indici mancanti e il famigerato problema N+1.
In questo articolo esamineremo le tecniche essenziali di ottimizzazione MySQL che ogni sviluppatore Laravel dovrebbe conoscere — quelle che trasformano una pagina da 3 secondi in una da 150 ms senza aggiornare un solo pezzo di hardware.
1. Il problema N+1: il killer silenzioso di Laravel
Questa è la causa più comune di applicazioni Laravel lente, ed è quasi sempre invisibile finché non la cerchi deliberatamente.
Considera questo codice dall'aspetto innocente:
Sembra a posto, vero? Tranne che questo genera N+1 query. Una query per recuperare tutti i post, poi una query aggiuntiva per ogni post per recuperare il suo autore. Con 100 post, sono 101 query. Con 1.000 post, sono 1.001 query.
La soluzione è l'eager loading:
Ora sono solo 2 query, indipendentemente da quanti post hai.
Come rilevare le query N+1
Laravel ha strumenti eccellenti per questo:
- Laravel Debugbar — mostra ogni query ed evidenzia i duplicati.
- Laravel Telescope — un compagno di debug completo con profilazione delle query.
- DB::listen() — registra manualmente ogni query.
Puoi anche abilitare la modalità strict in sviluppo per lanciare eccezioni sul lazy loading:
Questo ti costringe a individuare i problemi N+1 durante lo sviluppo invece che in produzione.
2. Gli indici: il più grande guadagno singolo
Se fai solo una cosa dopo aver letto questo articolo, falla: aggiungi indici alle tue chiavi esterne e alle colonne interrogate frequentemente.
Laravel lo rende facile con le migrazioni:
Una query che scansiona 1.000.000 di righe può scendere a scansionarne 100 con l'indice giusto. È un'accelerazione di 10.000 volte grazie a una singola riga di codice.
Come sapere quali indici ti servono
Usa EXPLAIN per vedere cosa sta facendo MySQL:
Cerca questi segnali d'allarme:
- type: ALL — scansione completa della tabella, nessun indice utilizzato.
- rows: 500000 — MySQL si aspetta di scansionare mezzo milione di righe.
- Extra: Using filesort — ordinamento in memoria o su disco invece di usare un indice.
- Extra: Using temporary — creazione di una tabella temporanea, di solito per GROUP BY.
La soluzione? Aggiungi un indice che corrisponda al pattern della query — di solito le colonne in WHERE, poi le colonne in ORDER BY.
3. Seleziona solo ciò che ti serve
Un altro errore classico:
Se ti servono solo nomi ed email, dillo:
Questo conta più di quanto pensi. Recuperare grandi colonne TEXT o JSON che non usi può moltiplicare sia l'uso della memoria che il tempo della query. E in Laravel, ogni modello Eloquent ha un overhead — costruire centinaia di modelli completi quando ti servono solo pochi campi è uno spreco.
Per dati veramente di sola lettura, considera di saltare completamente Eloquent:
Query Builder è significativamente più veloce di Eloquent quando non hai bisogno di modelli.
4. Usa chunk() e cursor() per grandi set di dati
Caricare un milione di righe in memoria farà crashare la tua applicazione — anche se la query stessa è veloce. Laravel fornisce due strumenti per lo streaming di grandi set di dati:
cursor() usa i generatori PHP ed è efficiente in memoria, ma mantiene una singola connessione al database aperta per tutta l'iterazione. chunk() esegue più query, il che è spesso più sicuro in produzione.
5. Evita le query nei cicli
Se ti ritrovi a scrivere questo:
Fermati. Sostituiscilo con una singola query:
Questo vale anche per update e delete:
La seconda versione è una query invece di centinaia — spesso la differenza tra 500 ms e 5 ms.
6. Attenzione con whereHas e withCount
whereHas() è comodo ma può generare costose sottoquery correlate:
Per semplici verifiche di esistenza, preferisci whereIn con una sottoquery:
Allo stesso modo, withCount() esegue una sottoquery per ogni relazione. Va bene per un paio di relazioni, ma se stai contando cinque relazioni nella stessa query, considera di ristrutturare.
7. Usa la cache in modo aggressivo
A volte la query più veloce è quella che non esegui affatto. Il livello di cache di Laravel è uno degli strumenti di performance più sottoutilizzati del framework.
Per dati che cambiano raramente — categorie, impostazioni, liste statiche — la cache è essenzialmente performance gratuita. Usa Redis o Memcached in produzione e ricorda di invalidare le chiavi quando i dati cambiano.
8. Analizza le query lente in produzione
MySQL ha uno strumento integrato per trovare query lente:
Dopo un giorno in produzione, esamina il log. Troverai i veri colpevoli — le query che non avresti mai immaginato essere lente, eseguite migliaia di volte al minuto.
In Laravel, puoi anche registrare le query lente per richiesta tramite un service provider:
9. Usa i tipi di dati corretti
I piccoli dettagli contano più di quanto pensi:
- Usa
INT UNSIGNEDper gli ID, nonVARCHAR. - Usa
DECIMALper il denaro, maiFLOAT. - Usa
ENUMoTINYINTper gli stati, nonVARCHAR(255). - Usa
CHAR(36)per gli UUID, nonVARCHAR(255). - Preferisci
DATETIMEoTIMESTAMPalle date come stringhe.
Colonne più piccole significano indici più piccoli, che significano ricerche più veloci. Si accumula.
10. Ottimizza relazioni e vincoli
Le migrazioni di Laravel rendono facile definire chiavi esterne:
Le chiavi esterne non solo preservano l'integrità dei dati — creano anche automaticamente un indice, che è spesso esattamente ciò che ti serve per le performance dei JOIN.
Un flusso di lavoro pratico di debug
Quando una pagina è lenta, segui questa sequenza:
- Installa Laravel Debugbar o Telescope. Vedi quante query vengono eseguite.
- Cerca pattern N+1. Aggiungi
with()ovunque una relazione venga acceduta in un ciclo. - Controlla il numero di query. Una pagina dovrebbe tipicamente eseguire meno di 20 query. Oltre 100 è un campanello d'allarme.
- Esegui EXPLAIN sulle query più lente. Cerca scansioni complete della tabella.
- Aggiungi indici. Fai corrispondere l'indice alle colonne usate in WHERE e ORDER BY.
- Metti in cache ciò che non cambia. Soprattutto aggregati e dati di configurazione.
- Misura di nuovo. Ripeti finché la pagina non è veloce.
Conclusione
Nove volte su dieci, «il database è lento» significa in realtà «le query sono lente». Prima di rivolgerti a un server più potente, a un motore di database diverso o a un servizio cloud gestito, passa un pomeriggio a ottimizzare le tue query. Troverai quasi sempre che i guadagni sono già lì — aspettano nel codice.
Eager loading, indici corretti, evitare i cicli e la cache risolveranno la maggior parte dei problemi di performance nelle applicazioni Laravel. Nessuna di queste tecniche richiede nuova infrastruttura. Richiedono solo consapevolezza.
Inizia dalla tua pagina più lenta. Lancia un log delle query. Correggi l'N+1. Aggiungi un indice mancante. Misura di nuovo. Poi ripeti — e guarda il tuo database diventare magicamente «più veloce» senza cambiare nulla di esso.