Performance
Query lente: misurarle prima di aggiungere un indice
Una query lenta si misura con EXPLAIN QUERY PLAN e cinque esecuzioni. Su 200.000 righe SQLite la lettura completa costa 13,2 ms, l'indice 0,29 ms.
In sintesi
- Prima di creare un indice leggi EXPLAIN QUERY PLAN: SCAN è lettura completa, SEARCH è indice.
- Il numero utile è la mediana di cinque esecuzioni, non la prima che hai visto.
- Su 200.000 righe SQLite l'indice su una colonna porta la query da 13,2 ms a 0,29 ms.
- Un LIKE con wildcard iniziale non usa l'indice: 52,49 ms anche con l'indice presente.
Una query lenta si misura prima di toccarla: EXPLAIN QUERY PLAN dice se il database legge tutta la tabella, e cinque esecuzioni ripetute danno un tempo mediano su cui ragionare. Su 200.000 righe SQLite la stessa condizione passa da 13,2 ms a 0,29 ms quando l'indice giusto esiste.
Tre numeri prima di un indice
| Cosa misuri | Dove lo leggi | Cosa ti dice |
|---|---|---|
| Piano di esecuzione | EXPLAIN QUERY PLAN | SCAN è lettura completa, SEARCH è indice |
| Tempo mediano | cinque esecuzioni, mediana | il numero che l'utente sente |
| Righe lette | EXPLAIN QUERY PLAN, campo detail | se il filtro è davvero selettivo |
Il comando
sqlite3 catalogue.db <<'SQL'
.timer on
EXPLAIN QUERY PLAN SELECT id FROM components WHERE library = 'lib-1000';
SELECT count(*) FROM components WHERE library = 'lib-1000';
CREATE INDEX idx_library ON components (library);
SELECT count(*) FROM components WHERE library = 'lib-1000';
SQL
Il comando richiede il client sqlite3 e un file di prova con la tabella components. .timer on stampa la durata di ogni istruzione: la prima SELECT legge tutta la tabella, la seconda usa l'indice creato nel frattempo. La nostra misura ha usato lo stesso schema con node e sqlite3, perché serviva la mediana di più esecuzioni, non una sola.
Quando l'indice non aiuta
Un indice non è una bacchetta: risolve l'uguaglianza e gli intervalli su una colonna, non un LIKE '%chart%'. La wildcard iniziale obbliga il motore a leggere ogni riga, ed è il motivo dei 52,49 ms misurati. Non aiuta nemmeno su una colonna con due valori distinti: metà tabella resta metà tabella. In quei casi si cambia il modello, si aggiunge una tabella di ricerca o si accetta il costo.
Il limite da dichiarare
La tabella era in memoria, senza I/O su disco e senza concorrenza: i millisecondi assoluti sono più bassi di quelli di un server vero. Il rapporto tra lettura completa e indice è la parte da portare via, non il valore assoluto. Un database in produzione aggiunge cache, lock e query concorrenti, e un indice in più rallenta ogni scrittura.
Dove continuare
Il catalogo EneUI indicizza 111.985 componenti, 1.556 blocchi e il record spotlight card, una scheda che costa una query per ogni richiesta. Per capire quanto pesa il tempo di risposta del server leggi TTFB: cosa misura, per la cache che evita la query Cache-Control spiegato, e per il confronto tra architetture framework frontend a confronto.
Domande frequenti
Come capisco se una query usa un indice?
Leggi EXPLAIN QUERY PLAN: SEARCH con USING INDEX significa indice usato, SCAN significa lettura completa della tabella. Il piano non mostra i tempi, solo il percorso.
Quante esecuzioni servono per un tempo affidabile?
Cinque, con la mediana come risultato. La prima esecuzione paga la cache fredda e il caricamento del motore, quindi è sempre la più lenta e la più fuorviante.
Perché LIKE '%chart%' resta lento con l'indice?
Perché la wildcard iniziale impedisce di usare l'ordine dell'indice: il motore deve leggere ogni riga e confrontare il testo. Un indice funziona quando il prefisso della stringa è noto.
Un indice rallenta le scritture?
Sì. Ogni INSERT, UPDATE e DELETE aggiorna anche l'indice, quindi il costo si sposta sulle scritture. Vale la pena quando le letture sono molte di più e il filtro è selettivo.
Il tempo misurato in locale vale in produzione?
No. Cache, disco, concorrenza e dimensione reale dei dati cambiano il numero. Misura il piano in locale e il tempo sul database di produzione, con gli stessi parametri.