Ottimizzazione delle query SQL: leggi i piani di esecuzione e correggi query lente, con tempistiche

  1. Home
  2. Blog
  3. Database e SQL

Database e SQL

Ottimizzazione delle query SQL: Leggere i piani di esecuzione e correggere query lente, con tempistiche

Smetti di indovinare perché una query è lenta. Impara a leggere un piano di esecuzione, quindi applica le correzioni più importanti, con misurazioni prima e dopo da una tabella reale di un milione di righe.

9 min di lettura
Ottimizzazione delle query SQL: leggi i piani di esecuzione e correggi le query lente, con tempistiche

Punti chiave

  • Inizia con il piano di esecuzione. Ti dice cosa fa effettivamente il database.
  • Cerca scansioni complete su tabelle di grandi dimensioni, quindi aggiungi indici che corrispondono ai tuoi filtri e join.
  • Evitare di racchiudere una colonna indicizzata in una funzione, poiché ciò potrebbe impedire un utilizzo efficiente dell’indice.
  • Misura prima e dopo, perché ogni database e set di dati si comporta in modo diverso.

Una query che l’anno scorso richiedeva millisecondi può richiedere minuti oggi, semplicemente perché la tabella è cresciuta. L’ottimizzazione delle query SQL riguarda meno trucchi intelligenti e più un processo ripetibile: scoprire cosa sta facendo il database, rimuovere il lavoro sprecato e verificare che sia stato d’aiuto.

Abbiamo testato ciascuna tecnica di seguito su un milione di righe generate in SQLite 3.50. I tempi sono i migliori tra diverse esecuzioni da un laptop, utilizzando un database in memoria. Non corrisponderanno al tuo sistema e PostgreSQL, MySQL e SQL Server hanno ciascuno i propri ottimizzatori e formati di piano. Concentrarsi sulla portata di ogni miglioramento e sui principi che ne stanno alla base.

Risultati in breve

Tecnica Prima Dopo
Indice sulla colonna del filtro 73 ms 0,01 ms
Intervallo anziché una funzione su una colonna 96 ms 33 ms
Indice composito per due filtri 19 ms 2,2 ms
Keyset anziché OFFSET profondo 8,1 ms 0,01 ms

L’impostazione della prova

CREATE TABLE customers (
  id INTEGER PRIMARY KEY, name TEXT, city TEXT
);
CREATE TABLE orders (
  id INTEGER PRIMARY KEY, customer_id INT, status TEXT,
  created TEXT, total INT, note TEXT
);
-- 50,000 customers and 1,000,000 orders with random values

Un semplice processo di ottimizzazione

  1. Trova le query che contano. Utilizza registri o monitoraggio delle query lenti. Una query di 200 ms eseguita mille volte al minuto può avere più importanza di un report mensile di 10 secondi.
  2. Leggi il piano. Scopri cosa sta realmente facendo il database.
  3. Cambia una cosa. Aggiungi un indice o riscrivi parte della query.
  4. Misurare nuovamente, su dati realistici. Un piano su 100 righe di prova dice molto poco su un milione.
  5. Guardalo nel tempo. I piani possono cambiare man mano che i dati crescono e le statistiche cambiano.

Leggere un piano di esecuzione

Un piano di esecuzione è la strategia passo passo del database per l’esecuzione di una query, inclusi gli indici utilizzati e l’ordine in cui unisce le tabelle. Prefisso una query per vederla:

Banca dati Comando
SQLite SPIEGARE QUERY PIANO SELEZIONA...
PostgreSQL SPIEGARE ANALIZZA SELEZIONA...
MySQL EXPLAIN SELECT ... o EXPLAIN ANALYZE
SQLServer Piano di esecuzione effettivo in SSMS

Tieni presente che EXPLAIN ANALYZE in PostgreSQL esegue effettivamente la query, quindi fai attenzione quando lo usi con UPDATE o DELETE. Ecco cosa cercare per primo:

Vedi Significato Di solito
Scansione o Scansione sequenziale Legge l’intera tabella Una preoccupazione sui grandi tavoli
Cerca o Cerca nell’indice Passa alle righe corrispondenti Bene
Indice di copertura Risposta solo dall’indice Molto buono
Temp B-tree o Ordina Lavoro di smistamento extra Vale la pena controllare

Una scansione completa non è automaticamente negativa. Se una query richiede gran parte di una tabella piccola, la scansione può essere la scelta più rapida. In PostgreSQL EXPLAIN ANALYZE, confronta anche le righe stimate con le righe effettive. Una grande differenza spesso significa che le statistiche della tabella non sono aggiornate.

Aggiungi l’indice giusto

Questa query trova gli ordini di un cliente:

SELECT id, total FROM orders WHERE customer_id = 123;

Prima di aggiungere un indice, il piano prevedeva una scansione completa:

SCAN orders
-- 20 rows in 73 ms

Quindi abbiamo aggiunto un indice nella colonna del filtro:

CREATE INDEX idx_orders_customer ON orders(customer_id);

SEARCH orders USING INDEX idx_orders_customer (customer_id=?)
-- 20 rows in 0.01 ms

Le stesse 20 righe tornavano migliaia di volte più velocemente, perché il database non leggeva più un milione di righe per trovarle. Nessun’altra tecnica in questo articolo ci ha dato una vittoria più grande. Buoni primi candidati sono le colonne utilizzate in WHERE, JOIN e ORDER BY, soprattutto quando restringono i risultati a poche righe. La nostra guida agli indici SQL spiega come funzionano.

Non racchiudere le colonne indicizzate nelle funzioni

Ecco due modi per trovare gli ordini di marzo, con un indice su created:

-- Function on the column
SELECT id FROM orders WHERE substr(created, 1, 7) = '2026-03';
-- SCAN orders USING COVERING INDEX idx_orders_created
-- 84,961 rows in 96 ms

-- Plain range
SELECT id FROM orders
WHERE created >= '2026-03-01' AND created < '2026-04-01';
-- SEARCH orders USING COVERING INDEX idx_orders_created (created>? AND created<?)
-- 84,961 rows in 33 ms

Entrambi restituiscono righe identiche, ma la prima ha impiegato circa tre volte il tempo. Il piano mostra il motivo: con una funzione sulla colonna, il database non può saltare a una posizione nell’indice ordinato, quindi deve SCAN attraverso ogni voce. La versione della gamma può SEARCH direttamente al posto giusto. Una condizione che può utilizzare un indice in questo modo è chiamata sargable.

Lo stesso problema si presenta con questi modelli comuni:

Evitare Preferisco
DOVE ANNO(creato) = 2026 DOVE creato >= '2026-01-01' E creato < '2027-01-01'
DOVE prezzo * 2 > 100 DOVE prezzo > 50
DOVE LOWER(e-mail) = 'a@b.com' Un indice basato su funzioni o regole di confronto senza distinzione tra maiuscole e minuscole
DOVE nome COME '%99' Una ricerca del prefisso o una ricerca del testo completo

Abbiamo controllato anche quest’ultimo. Viene prodotto un carattere jolly iniziale SCAN customers, poiché un indice ordinato a partire dalla prima lettera non può aiutare con una corrispondenza alla fine.

Indici compositi

Quando una query viene filtrata su due colonne, un indice a colonna singola svolge solo parte del lavoro:

SELECT id FROM orders
WHERE status = 'paid'
  AND created >= '2026-03-01' AND created < '2026-03-08';

-- Using only the created index: 6,198 rows in 19 ms

CREATE INDEX idx_status_created ON orders(status, created);

-- SEARCH orders USING COVERING INDEX idx_status_created
--   (status=? AND created>? AND created<?)
-- 6,198 rows in 2.2 ms

L’ordine delle colonne è molto importante. Un indice su (status, created) funziona come un elenco telefonico ordinato per cognome e poi per nome. Puoi cercare solo il cognome, ma non solo il nome. La nostra query su status da sola potrebbe utilizzare questo indice. Il filtraggio solo su created in genere non è in grado di utilizzarlo in modo efficiente. Come regola generale, metti prima le colonne testate con l’uguaglianza e dopo di esse le colonne con intervallo.

Quel piano dice anche indice di copertura, il che significa che l’indice conteneva tutto ciò di cui la query aveva bisogno, quindi non era necessario leggere la tabella stessa. La selezione solo delle colonne necessarie rende ciò più probabile, il che è un altro motivo per evitarlo SELECT *.

Impaginazione più veloce

L’utilizzo di OFFSET per le pagine profonde è un costo nascosto, poiché il database deve ancora superare tutte le righe saltate:

-- Page deep into the results
SELECT id FROM orders ORDER BY id LIMIT 20 OFFSET 500000;
-- 8.1 ms

-- Keyset pagination: continue from the last id seen
SELECT id FROM orders WHERE id > 500000 ORDER BY id LIMIT 20;
-- 0.01 ms

L’impaginazione dei keyset rimane veloce, non importa quanto vai in profondità. Inoltre, evita che le righe si spostino tra le pagine quando vengono aggiunti nuovi dati. Il compromesso è che gli utenti non possono passare direttamente alla pagina 5.000, il che raramente è una reale necessità. Nelle tabelle con righe larghe o con più righe da saltare, il divario può essere molto maggiore rispetto al nostro test.

Suggerimento: I nostri tempi misurano una query alla volta su un computer inattivo. In condizioni di carico reale, una query inefficiente provoca più danni, perché mantiene anche i blocchi e compete con tutto il resto per memoria, disco e CPU.

Scrivi query più snelle

  • Seleziona solo le colonne che ti servono. SELECT * invia più dati e può impedire a un indice di coprire la query.
  • Filtra in anticipo. Utilizza WHERE per rimuovere le righe prima del raggruppamento e HAVING solo per le condizioni sugli aggregati. Vedere SQL GROUP BY e HAVING.
  • Utilizza LIMIT quando ti servono solo poche righe.
  • Preferisci UNION ALL quando non è necessario rimuovere i duplicati. Il nostro test UNION vs UNION ALL mostra cosa si risparmia.
  • Evita DISTINCT non necessari. Spesso nasconde un join che produce duplicati.
  • Utilizza EXISTS per i controlli di esistenza. Può arrestarsi alla prima corrispondenza, come spiega la nostra guida alle sottoquery SQL.
  • Cerca N+1 query. Una query per riga in un ciclo, solitamente causata dal codice dell’applicazione, è spesso il vero problema. Una singola query di unione o batch può sostituire centinaia di viaggi di andata e ritorno.

Si unisce

Indicizza le colonne a cui ti iscrivi. Abbiamo eseguito un report sulla spesa totale per i clienti in una città e il piano era esattamente quello che speravi:

SELECT c.name, SUM(o.total)
FROM customers c
JOIN orders o ON o.customer_id = c.id
WHERE c.city = 'Goa'
GROUP BY c.id;

-- SCAN c
-- SEARCH o USING COVERING INDEX idx_cover (customer_id=?)
-- 9,900 rows in 34 ms

Esegue la scansione dei clienti, quindi utilizza un indice per trovare gli ordini di ciascuno. Senza un indice su orders.customer_id, il database dovrebbe svolgere molto più lavoro per ciascun cliente. Un indice sulla chiave esterna è una delle aggiunte più comuni e preziose che puoi apportare. Per rinfrescare la tua comprensione delle nozioni di base, consulta Spiegazione dei join SQL.

Quando gli indici ti costano

Gli indici non sono gratuiti, quindi non aggiungerne uno per ogni colonna:

  • Scritture più lente. Ogni inserimento, aggiornamento ed eliminazione deve aggiornare anche ogni indice.
  • Più spazio di archiviazione e memoria. Gli indici possono diventare grandi su tabelle di grandi dimensioni.
  • Poco vantaggio sulle colonne non selettive. Un indice su una colonna true o false potrebbe non essere d’aiuto se la maggior parte delle righe corrisponde.
  • Ridondanza. Un indice su (a) spesso non è necessario se ne hai già uno su (a, b).

Controlla periodicamente le statistiche di utilizzo e rimuovi gli indici che non vengono utilizzati. E mantieni aggiornate le statistiche con ANALYZE o l’equivalente del tuo database, perché l’ottimizzatore fa affidamento su di loro per scegliere un buon piano. A volte la parte lenta è la contesa piuttosto che la domanda. Le transazioni di lunga durata che mantengono i blocchi possono far sembrare lenta una query veloce.

Errori comuni

  • Ottimizzare senza misurare. Molte ipotesi attendibili si rivelano errate.
  • Test con dati di piccole dimensioni. I piani e le tempistiche cambiano drasticamente man mano che le tabelle crescono.
  • Affidarsi a un suggerimento per un altro database. I consigli per un motore potrebbero non essere applicabili a un altro.
  • Aggiunta di indici alla cieca. Ognuno di essi rallenta le scritture.
  • Ignorando l’effetto della prima esecuzione. Le cache rendono la seconda esecuzione più veloce, quindi esegui più volte.

Considerazioni finali

La maggior parte delle query lente ha cause ordinarie: un indice mancante, una funzione su una colonna o troppi dati recuperati. Prendi la tua query reale più lenta questa settimana, esegui EXPLAIN su di essa e cerca una scansione completa su una tabella di grandi dimensioni. Correggi una cosa, misura di nuovo e prendi nota di ciò che hai modificato.

Domande frequenti

Cos’è l’ottimizzazione delle query SQL?

È il processo che fa sì che le query utilizzino meno tempo e meno risorse, in genere esaminando i piani di esecuzione, aggiungendo indici adeguati e riscrivendo query inefficienti.

Cos’è un piano di esecuzione?

È la strategia scelta dal database per eseguire una query, incluso il modo in cui legge le tabelle, quali indici utilizza e in quale ordine si unisce.

Come trovo le query lente?

Abilita il registro delle query lente del tuo database o utilizza strumenti come pg_stat_statements in PostgreSQL. Quindi concentrati sulle query con il tempo totale più elevato.

L’aggiunta di un indice rende sempre le query più veloci?

No. Aiuta le letture selettive ma rallenta le scritture e utilizza lo spazio di archiviazione. L’ottimizzatore potrebbe anche ignorarlo se la maggior parte delle righe soddisfa la condizione.

SELECT * è dannoso per le prestazioni?

Può essere. Trasferisce dati non necessari e può impedire a un indice di coprire la query. Di solito è meglio elencare solo le colonne che ti servono.

Continua a imparare

Lavora con Stack Quora

Hai bisogno di aiuto con test, WordPress o SEO?

Aiutiamo i team a fornire software migliore e a far crescere i loro siti. Raccontaci a cosa stai lavorando e ti suggeriremo chiaramente il prossimo passo.

Continua a leggere

Altre guide su test, intelligenza artificiale e sviluppo moderno

Ti è piaciuto questo articolo? Scegli un argomento e passa direttamente ai nostri tutorial più recenti.

Intelligenza artificiale

Spiegazione delle allucinazioni dell'intelligenza artificiale: perché i modelli linguistici inventano le cose e come ridurle

Spiegazione delle allucinazioni dell'intelligenza artificiale: cosa sono, perché i modelli linguistici le producono, tipi comuni, come ridurle con b…

Leggi l'articolo →
DevOps

Spiegazione delle variabili d'ambiente: file .env, segreti e configurazione eseguiti correttamente

Variabili d'ambiente spiegate con esempi Python che abbiamo eseguito: lettura e impostazione, perché ogni valore è una stringa, file .env, ereditari…

Leggi l'articolo →
Sviluppo software

Spiegazione della limitazione della velocità API: algoritmi, intestazioni e gestione degli errori 429

La limitazione della velocità dell'API spiegata con una simulazione che abbiamo eseguito: bucket di token, finestra fissa, finestra scorrevole, risp…

Leggi l'articolo →
Automazione dei test

Derisione in Python: unittest.mock, patch e gli errori che fanno mentire i test

Mocking in Python con unittest.mock, testato: Mock e return_value, side_effect, patch dove viene utilizzato un nome, specifiche e autospec, date moc…

Leggi l'articolo →
Test del software

Spiegazione della copertura del codice: copertura di linea o di filiale e cosa significa realmente il 100%.

Copertura del codice spiegata con una vera esecuzione di cover.py: copertura di linea, ramo e condizione, lettura di un rapporto, perché la copertur…

Leggi l'articolo →
Database e SQL

DELETE vs TRUNCATE vs DROP in SQL: differenze, rollback e quando utilizzarli

DELETE vs TRUNCATE vs DROP spiegato con SQL testato: cosa rimuove, rollback, trigger, reimpostazione dell'identità, velocità e come differiscono Pos…

Leggi l'articolo →
Database e SQL

Spiegazione delle visualizzazioni SQL: crea, utilizza ed evita le insidie, con esempi

Viste SQL spiegate con le query che abbiamo eseguito: creazione di una vista, risultati in tempo reale, nascondere colonne, prestazioni, viste aggio…

Leggi l'articolo →
Sviluppo software

Interfaccia TypeScript e tipo: differenze reali, testate con il compilatore

Interfaccia TypeScript vs tipo, verificata con il vero compilatore: cosa possono fare entrambi, cosa può fare solo uno, fusione delle dichiarazioni,…

Leggi l'articolo →
Sviluppo software

Spiegazione di Python *args e **kwargs: funzioni flessibili con esempi chiari

Python *args e **kwargs spiegati con il codice che abbiamo eseguito: compressione e decompressione, ordine dei parametri, argomenti solo per parole …

Leggi l'articolo →
Sviluppo software

Funzioni Lambda Python: sintassi, usi reali e quando non usarli

Funzioni lambda Python spiegate con il codice che abbiamo eseguito: sintassi, ordinamento con chiave, mappa e filtro, chiusure, trap dell'associazio…

Leggi l'articolo →
Intelligenza artificiale

Cosa sono gli agenti AI? Come funzionano, con un semplice ciclo di agenti che puoi eseguire

Cosa sono gli agenti IA? Una semplice guida al ciclo dell'agente, agli strumenti, alla memoria e ai guardrail, con un piccolo ciclo Python che abbia…

Leggi l'articolo →
DevOps

Monitoraggio vs osservabilità: la vera differenza, con log, metriche e tracce

Spiegazione del monitoraggio e dell'osservabilità: errori noti e sconosciuti, log, metriche e tracce, una procedura dettagliata sugli incidenti di p…

Leggi l'articolo →
DevOps

Volumi Docker e montaggi di associazione: mantenere i dati del contenitore nel modo giusto

Volumi Docker e montaggi di collegamento spiegati: perché i dati del contenitore scompaiono, volumi denominati, montaggi di collegamento, tmpfs, ese…

Leggi l'articolo →
Automazione dei test

TDD vs BDD: differenze chiave, una caratteristica costruita in entrambi i modi e quando utilizzarli ciascuno

TDD vs BDD spiegato con una funzione di sconto costruita in entrambi i modi: refactoring rosso-verde con il codice che abbiamo eseguito, scenari "Da…

Leggi l'articolo →
Test del software

Test di accettazione da parte dell'utente (UAT): processo, lista di controllo ed esempi reali

Spiegazione del test di accettazione dell'utente: chi lo esegue, in cosa differisce dal QA, un processo in sette fasi, un modello di test case UAT, …

Leggi l'articolo →
Database e SQL

Spiegazione dei valori SQL NULL: IS NULL, COALESCE, NULLIF e le trappole da evitare

Valori SQL NULL spiegati con le query che abbiamo eseguito: logica a tre valori, IS NULL, COALESCE, NULLIF, come aggregazioni, join e ordinamento tr…

Leggi l'articolo →
Database e SQL

Istruzione SQL CASE WHEN: sintassi, esempi reali ed errori comuni

SQL CASE WHEN spiegato con le query che abbiamo eseguito: moduli semplici e cercati, ordine delle condizioni, pivot con SUM, trucchi COUNT, ordiname…

Leggi l'articolo →
Sviluppo software

Cos'è il JWT? Token Web JSON spiegati con un esempio funzionante

Cos'è il JWT? Spiegazione dei token Web JSON: le tre parti, un token creato e manomesso nel codice che abbiamo eseguito, JWT e sessioni, archiviazio…

Leggi l'articolo →
Sviluppo software

Spiegazione del loop di eventi JavaScript: stack di chiamate, microtask e timer con esempi

Il ciclo di eventi JavaScript spiegato con il codice che abbiamo eseguito: stack di chiamate, code di attività e microtask, perché setTimeout 0 non …

Leggi l'articolo →
Sviluppo software

Spiegazione dei generatori Python e del rendimento: risparmia memoria con esempi reali

Generatori Python e rendimento spiegati con il codice che abbiamo eseguito: come vengono messi in pausa e ripresi, misurato il risparmio di memoria,…

Leggi l'articolo →
Intelligenza artificiale

Cosa sono gli incorporamenti? I vettori e la somiglianza del coseno spiegati semplicemente

Cosa sono gli incorporamenti? Una guida in linguaggio semplice ai vettori, alla similarità del coseno e alla ricerca semantica, con un piccolo esemp…

Leggi l'articolo →
DevOps

Distribuzione blu-verde rispetto a quella canaria: differenze, compromessi e come scegliere

Spiegazione della distribuzione blu-verde e canary: come funziona ciascuna, costi, rischi e rollback, bucket utente stabile che abbiamo testato, mig…

Leggi l'articolo →
Automazione dei test

Test instabili: quali sono le cause e come risolverli definitivamente

Test instabili spiegati con demo che abbiamo eseguito: le cause principali, come confermarne e diagnosticarne uno, soluzioni adeguate per tempistich…

Leggi l'articolo →
Test del software

Test di carico e test di stress: differenze, metriche ed esempi k6

Spiegazione del test di carico e dello stress test: cosa dimostra ciascuno, test di picco e di assorbimento, le metriche che contano, perché le medi…

Leggi l'articolo →
Test del software

Caso di test e scenario di test: differenze chiave, esempi e quando utilizzarli

Spiegazione del caso di test e dello scenario di test: definizioni chiare, una tabella affiancata, una funzione di reimpostazione della password pre…

Leggi l'articolo →
Database e SQL

Ottimizzazione delle query SQL: leggi i piani di esecuzione e correggi query lente, con tempistiche

Ottimizzazione delle query SQL testata su un milione di righe: leggi i piani di esecuzione, aggiungi gli indici corretti, correggi filtri non aggior…

Leggi l'articolo →
Database e SQL

SQL UNION vs UNION ALL: differenze, esempi e prestazioni testate

SQL UNION vs UNION ALL spiegati con le query che abbiamo eseguito: duplicati, regole seguite da entrambi, un vero bug di dati, prestazioni misurate,…

Leggi l'articolo →
Sviluppo software

Tutorial sull'API di recupero JavaScript: GET, POST, gestione degli errori e timeout

Un tutorial sull'API JavaScript Fetch testato su un server locale: GET e POST, perché 404 non genera eccezioni, un helper riutilizzabile, stringhe d…

Leggi l'articolo →
Sviluppo software

Gestione dei file Python: lettura, scrittura, CSV, JSON e pathlib con esempi

Gestione dei file Python spiegata con il codice che abbiamo eseguito: modalità aperte, con, lettura di file di grandi dimensioni, CSV, JSON, pathlib…

Leggi l'articolo →
Sviluppo software

Git merge vs rebase: differenze, esempi e quando usare ciascuno

Git merge vs rebase spiegato con output reale del log, una tabella di confronto, la regola d'oro del rebase e un flusso di lavoro sicuro per i team.

Leggi l'articolo →
Sviluppo software

List comprehension in Python: sintassi, esempi e quando evitarla

List comprehension in Python spiegata: sintassi, filtri, if-else, cicli annidati, tempi misurati e i casi in cui un normale ciclo for è meglio.

Leggi l'articolo →
Database e SQL

SQL JOIN spiegati con esempi (Inner, Left, Right, Full)

Capisci i JOIN SQL passo dopo passo con tabelle e query di esempio: INNER, LEFT, RIGHT, FULL, CROSS e SELF, con errori comuni e consigli sulle prest…

Leggi l'articolo →