← Torna agli articoli
Performance e database

Indici del database: i supereroi invisibili della tua app. Spiegati semplicemente con codice Laravel e avvertimenti importanti

Una guida chiara agli indici del database con esempi Laravel: come funzionano, quando aggiungerli e gli avvertimenti critici che ogni sviluppatore deve conoscere su indici compositi, a bassa selettività e inutilizzati.

✦
Articolo in evidenza ↗

Indici del database: i supereroi invisibili della tua app. Spiegati semplicemente con codice Laravel e avvertimenti importanti

Immagina di cercare una parola specifica in un libro di 1.000 pagine — ma il libro non ha né indice all'inizio né indice analitico alla fine. Dovresti leggere ogni pagina. Questo è esattamente ciò che fa il tuo database quando esegue una query su una colonna senza indice.

Gli indici sono i supereroi invisibili della tua applicazione. Quando fanno il loro lavoro, nessuno se ne accorge. Quando mancano, tutto si blocca. Analizziamo cosa sono, come funzionano e — cosa più importante — gli avvertimenti che ogni sviluppatore deve conoscere.

📚 Cos'è un indice del database?

Un indice del database è una struttura dati speciale (di solito un B-tree) che il database mantiene accanto ai dati della tua tabella. Memorizza una copia ordinata di una o più colonne, più puntatori alle righe originali.

Pensalo come l'indice alfabetico alla fine di un manuale: invece di scansionare ogni pagina, salti direttamente alla voce rilevante.

🎯 Perché gli indici contano

  • Velocizzano le clausole WHERE — trovare righe tramite una condizione diventa O(log n) invece di O(n).
  • Velocizzano le operazioni JOIN — le chiavi esterne traggono enormi benefici dagli indici.
  • Velocizzano ORDER BY — se l'ordinamento corrisponde a un indice, non è necessario alcun ordinamento aggiuntivo.
  • Garantiscono l'unicità — gli indici univoci garantiscono l'integrità dei dati a livello di archiviazione.

Senza un indice, MySQL/PostgreSQL deve eseguire una scansione completa della tabella. Su una tabella con milioni di righe, è la differenza tra 5 ms e 5 secondi.

🛠 Creare indici in Laravel

Il sistema di migrazioni di Laravel rende la creazione degli indici pulita ed espressiva.

1. Indice su una singola colonna:

Schema::table('users', function (Blueprint $table) { $table->index('email'); // Indice semplice });

2. Indice univoco:

Schema::table('users', function (Blueprint $table) { $table->unique('email'); // Garantisce l'unicità });

3. Indice composito (più colonne):

Schema::table('orders', function (Blueprint $table) { $table->index(['user_id', 'created_at']); // Indice composito });

4. Indice con nome personalizzato:

Schema::table('posts', function (Blueprint $table) { $table->index('status', 'idx_posts_status'); });

5. Chiave esterna con indice automatico:

Schema::table('posts', function (Blueprint $table) { $table->foreignId('user_id')->constrained(); // Crea automaticamente un indice });

🔍 Come verificare che un indice venga utilizzato

Usa EXPLAIN per vedere il piano di esecuzione:

EXPLAIN SELECT * FROM users WHERE email = 'user@example.com'; -- Output (indice utilizzato): -- Index Scan using users_email_index on users (cost=0.43..8.45 rows=1 width=...) -- Index Cond: ((email)::text = 'user@example.com'::text) -- Output (nessun indice, scansione completa): -- Seq Scan on users (cost=0.00..1500.00 rows=1 width=...) -- Filter: ((email)::text = 'user@example.com'::text)

In Laravel:

$plan = DB::select('EXPLAIN SELECT * FROM users WHERE email = ?', ['user@example.com']); dd($plan);

Cerca Index Scan o Index Only Scan — bene. Cerca Seq Scan — male, a meno che la tabella non sia minuscola.

⚠️ Avvertimenti importanti che ogni sviluppatore deve conoscere

1. Gli indici rallentano le scritture

Ogni INSERT, UPDATE e DELETE deve anche aggiornare ogni indice su quella tabella. Una tabella con 10 indici esegue le scritture circa 10 volte più lentamente di una tabella senza. Indicizza solo le colonne che interroghi, non le colonne che immagini di poter interrogare.

2. La regola del "prefisso più a sinistra" per gli indici compositi

Un indice su (a, b, c) può essere usato per query su a, (a, b) o (a, b, c). Non può essere usato efficientemente per query solo su b o (b, c). L'ordine conta.

// Questo usa l'indice composito (user_id, created_at): SELECT * FROM orders WHERE user_id = 5 AND created_at > '2024-01-01'; // Questo NON lo usa efficientemente: SELECT * FROM orders WHERE created_at > '2024-01-01';

3. Gli indici su colonne a bassa selettività sono inutili

Una colonna booleana con il 99% di true e l'1% di false è un cattivo candidato per l'indice. L'ottimizzatore spesso lo ignorerà ed eseguirà comunque una scansione completa, perché scansionare l'indice più cercare le righe costa più che scansionare semplicemente la tabella.

4. Le funzioni sulle colonne indicizzate uccidono l'indice

// MALE — l'indice su `email` NON viene utilizzato: SELECT * FROM users WHERE LOWER(email) = 'user@example.com'; // BENE — l'indice su `email` VIENE utilizzato: SELECT * FROM users WHERE email = 'user@example.com';

Soluzioni: memorizza dati normalizzati, o usa indici funzionali/espressione (PostgreSQL li supporta; MySQL 8+ li supporta).

5. Troppi indici sono dannosi quanto troppo pochi

Ogni indice occupa spazio su disco e rallenta le scritture. Ancora più importante, l'ottimizzatore deve considerare più opzioni quando pianifica una query, il che può portare a decisioni peggiori. Controlla regolarmente i tuoi indici.

6. Gli indici inutilizzati sono puro overhead

Trova gli indici che non vengono mai usati ed eliminali. In PostgreSQL:

SELECT schemaname, relname AS table_name, indexrelname AS index_name, idx_scan AS times_used FROM pg_stat_user_indexes WHERE idx_scan = 0 ORDER BY pg_relation_size(indexrelid) DESC;

In MySQL:

SELECT * FROM sys.schema_unused_indexes;

7. La selettività dell'indice è più importante del numero di indici

Un indice è più prezioso quando filtra fino a una piccola frazione di righe. Una query che restituisce l'1% di una tabella ne beneficia enormemente. Una query che restituisce l'80% no — il database probabilmente scansionerà la tabella invece.

🚀 Uno scenario realistico di ottimizzazione in Laravel

Considera un'app e-commerce con una pagina lenta "ordini recenti per utente":

// Query lenta $orders = Order::where('user_id', $user->id) ->orderByDesc('created_at') ->take(20) ->get();

Su una tabella con 5 milioni di ordini, questa query potrebbe richiedere secondi. Aggiungi un indice composito:

Schema::table('orders', function (Blueprint $table) { $table->index(['user_id', 'created_at']); });

Ora la stessa query viene eseguita in millisecondi a una cifra, perché l'indice fornisce sia il filtro che l'ordine di ordinamento.

📊 Cheat sheet degli indici

  • Chiavi esterne → indicizza sempre.
  • Colonne filtrate frequentemente → indicizza se selettive.
  • Colonne di ordinamento in ORDER BY → indicizza se usate spesso.
  • Colonne a bassa selettività (booleani, stati con pochi valori) → di solito da evitare.
  • Ordine negli indici compositi → colonne più selettive / più usate per prime, seguendo la regola del prefisso più a sinistra.
  • Controlla regolarmente → elimina gli indici inutilizzati; costano più di quanto danno.

💡 Consigli pratici

  1. Inizia con le chiavi esterne. Sono gli indici con il ROI più alto che aggiungerai mai.
  2. Misura prima e dopo. Usa EXPLAIN ANALYZE per confermare il miglioramento.
  3. Testa con dati di dimensioni di produzione. Una tabella di test da 1.000 righe non rivelerà mai un problema di indice.
  4. Osserva il percorso di scrittura. Se gli inserimenti rallentano, uno dei tuoi indici potrebbe essere il colpevole.
  5. Preferisci meno indici compositi ben scelti a molti indici a colonna singola.
  6. Non indicizzare per intuizione. Indicizza ciò di cui il pianificatore di query ha effettivamente bisogno.

🔗 Conclusione

Gli indici sono il motore silenzioso delle applicazioni ad alte prestazioni. Trasformano query che strisciano in ricerche istantanee, proteggono il tuo database da carichi incontrollati e supportano silenziosamente ogni pagina veloce che i tuoi utenti vedono.

Ma non sono gratuiti. Ogni indice è uno scambio: velocità di lettura in cambio di costo di scrittura e spazio su disco. Aggiungili deliberatamente, verifica che vengano usati e pulisci quelli che non lo sono. Padroneggia questa disciplina, e la tua applicazione scalerà — elegantemente, invisibilmente e a velocità.

Tecnologie e argomenti

Tag dell'articolo

Nessun articolo corrisponde a questi filtri.

Hai un progetto o un'idea da discutere?

Parliamone ↗