Per trovare e ottimizzare le query lente in PostgreSQL, devi abilitare l’estensione pg_stat_statements per identificarne i tempi di esecuzione e usare EXPLAIN ANALYZE per leggere il piano di esecuzione. Aggiungi gli indici mancanti con CREATE INDEX CONCURRENTLY e mantieni le statistiche aggiornate con VACUUM ANALYZE.
| Passo | Strumento | Comando chiave | Risultato |
|—|—|—|—|
| 1. Identificare | pg_stat_statements | SELECT query, total_exec_time/calls... | Trova la query più lenta |
| 2. Analizzare | EXPLAIN ANALYZE | EXPLAIN (ANALYZE, BUFFERS)... | Legge il piano di esecuzione |
| 3. Risolvere | Indici | CREATE INDEX CONCURRENTLY... | Elimina i sequential scan |
| 4. Mantenere | VACUUM ANALYZE | VACUUM ANALYZE nome_tabella; | Aggiorna le statistiche |
Quando ho analizzato il database di un ente sanitario pubblico con 2.000 workstation, le API interne impiegavano 4 secondi per rispondere. Gli operatori sanitari aspettavano minuti per caricare una cartella clinica. Il colpevole? Cinque query senza indici che eseguivano sequential scan su tabelle da 5 milioni di righe. Dopo aver applicato le tecniche che troverai in questa guida, i tempi di risposta sono scesi a 200 ms. Il 40% dei problemi di performance nei database relazionali deriva da query non ottimizzate e indici mancanti (Gartner 2025). Ecco come risolverli alla radice.
Il problema: le query lente PostgreSQL che vanno bene in dev e muoiono in produzione
L’ambiente di sviluppo ha 100 righe di test. La produzione ha 5 milioni di righe e 50 utenti concorrenti. Una query che impiega 2 millisecondi in sviluppo può richiedere 30 secondi in produzione. Il database cresce. Le query degradano in modo silenzioso. Nessuno se ne accorge finché gli utenti non segnalano lentezza. Il problema si aggrava perché l’ottimizzatore di PostgreSQL basa le sue decisioni sulle statistiche. Se le statistiche sono vecchie, PostgreSQL sceglie il piano sbagliato.
pg_stat_statements — abilitare il registro delle query lente
Per trovare le query lente, devi prima registrarle. pg_stat_statements è l’estensione ufficiale di PostgreSQL. Traccia ogni query eseguita sul database e ne salva i tempi di esecuzione.
Abilita l’estensione nel file postgresql.conf:
shared_preload_libraries = 'pg_stat_statements'
pg_stat_statements.track = all
Imposta una soglia per loggare le query che superano un certo tempo. Per registrare tutte le query che impiegano più di 1 secondo:
log_min_duration_statement = 1000
Riavvia PostgreSQL. Crea l’estensione nel database:
CREATE EXTENSION pg_stat_statements;
Ora puoi trovare le 10 query più lenti per tempo medio di esecuzione:
SELECT query, calls, total_exec_time/calls AS avg_ms, rows/calls AS avg_rows
FROM pg_stat_statements ORDER BY avg_ms DESC LIMIT 10;
Questo comando ti restituisce il tempo medio in millisecondi. Cerca le query con un alto numero di chiamate e un avg_ms elevato. Sono i tuoi colpevoli.
EXPLAIN e EXPLAIN ANALYZE — leggere il piano di esecuzione
Hai trovato la query lente. Ora devi capire perché è lenta. EXPLAIN mostra il piano stimato da PostgreSQL. EXPLAIN ANALYZE esegue la query e mostra il piano reale con i tempi effettivi.
Esegui il comando sulla tua query problematica:
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT) SELECT * FROM ordini WHERE cliente_id = 123;
Leggi l’output dal basso verso l’alto. Cerca le righe con un actual time elevato. Se vedi Seq Scan, PostgreSQL sta leggendo tutta la tabella riga per riga. È l’operazione più costosa. Cerca anche Filter. Significa che PostgreSQL legge più righe del necessario e le scarta dopo. La riga Rows Removed by Filter ti dice quante righe ha letto inutilmente.
Attenzione a EXPLAIN ANALYZE. Esegue la query. Se stai analizzando un UPDATE o un DELETE, avvolgilo in una transazione e fai un rollback per non modificare i dati.
Indici mancanti: come trovarli e crearli
Se EXPLAIN ANALYZE mostra un Seq Scan su una colonna usata nella clausola WHERE, manca un indice. Prima di creare l’indice, verifica se esiste già:
SELECT indexname FROM pg_indexes WHERE tablename = 'ordini';
Se l’indice non c’è, crealo. In produzione, usa sempre CONCURRENTLY. Crea l’indice senza bloccare le scritture sulla tabella:
CREATE INDEX CONCURRENTLY idx_ordini_cliente ON ordini(cliente_id);
La creazione concorrente richiede più tempo, ma non causa downtime. Dopo aver creato l’indice, esegui di nuovo EXPLAIN ANALYZE. Dovresti vedere Index Scan al posto di Seq Scan. Il tempo di esecuzione crollerà.
Sequential scan vs Index scan — quando è normale e quando è un problema
Non tutti i sequential scan sono un problema. Se PostgreSQL deve leggere il 90% delle righe di una tabella, un sequential scan è più veloce di un index scan. Perché? Leggere l’indice e poi accedere alla tabella richiede I/O extra. L’ottimizzatore fa questa scelta in autonomia.
Il sequential scan è un problema quando la query seleziona poche righe. Se cerchi un cliente_id specifico in una tabella da 5 milioni di righe, un sequential scan è un disastro. La regola pratica: se la query restituisce meno del 5-10% delle righe totali della tabella, serve un index scan. Se restituisce il 90% delle righe, il sequential scan è corretto.
Query con JOIN lenti — ottimizzare le relazioni
I JOIN lenti derivano spesso da tabelle piccole che moltiplicano le righe (cartesian product) o da join su colonne senza indice. Controlla il piano di esecuzione. Se vedi Hash Join o Nested Loop con un actual rows molto superiore a quello stimato, le statistiche sono vecchie.
Assicurati che le colonne usate nella condizione di JOIN siano indicizzate. Per i JOIN frequenti, crea un indice composito sulle due colonne. Un errore comune è fare il join su colonne con tipi di dati diversi. Se una è integer e l’altra è varchar, PostgreSQL non può usare l’indice e esegue un cast esplicito, forzando un sequential scan.
VACUUM e ANALYZE — mantenere le statistiche aggiornate
PostgreSQL usa le statistiche per decidere il piano di esecuzione. Se le statistiche sono vecchie, le decisioni sono sbagliate. Il 68% degli sviluppatori usa EXPLAIN ANALYZE regolarmente per il tuning (State of PostgreSQL Survey 2025), ma dimentica di aggiornare le statistiche prima.
ANALYZE raccoglie le statistiche. VACUUM recupera lo spazio dalle righe morte. Esegui entrambi:
VACUUM ANALYZE nome_tabella;
Eseguilo dopo grandi caricamenti di dati, dopo aver creato o eliminato indici, o se vedi una grande differenza tra le righe stimate e le righe reali in EXPLAIN ANALYZE. Non aspettare l’autovacuum. Su tabelle grandi con molte scritture, l’autovacuum potrebbe non girare abbastanza spesso.
Configurazioni postgresql.conf che impattano le performance
Due parametri in postgresql.conf cambiano le performance delle query.
shared_buffers definisce quanto spazio in RAM PostgreSQL usa per i dati. Il valore di default è basso. Su un server dedicato, impostalo al 25% della RAM totale.
work_mem definisce la memoria per le operazioni di ordinamento e hash. Se le query fanno ORDER BY o JOIN su grandi dataset, un valore basso forza PostgreSQL a scrivere su disco. Aumenta work_mem con cautela. Troppa memoria allocata per 50 connessioni simultanee esaurisce la RAM.
Imposta anche effective_cache_size al 75% della RAM. Non alloca memoria, ma dice a PostgreSQL quanta memoria il sistema operativo ha disponibile per la cache. Aiuta l’ottimizzatore a scegliere i piani di esecuzione.
Strumenti: pgBadger, pgAdmin, pg_activity
Per analizzare i log in modo efficiente, usa pgBadger. Genera report HTML dai log di PostgreSQL. Mostra le query più lenti, gli errori e i checkpoint.
pgAdmin ha un grafico integrato per pg_stat_statements. Ti permette di visualizzare le query lenti senza scrivere SQL.
Per il monitoraggio in tempo reale, usa pg_activity. È come htop per PostgreSQL. Mostra le query in esecuzione, il tempo di esecuzione e le lock. Lo installi con pip install pg_activity.
Documentazione ufficiale di pg_stat_statements: https://www.postgresql.org/docs/current/pgstatstatements.html
Errori comuni e troubleshooting
L’errore più frequente è creare indici senza CONCURRENTLY in produzione. Blocca le scritture sulla tabella per tutta la durata della creazione. Se la tabella ha milioni di righe, il blocco dura minuti.
Un altro errore è eseguire EXPLAIN senza ANALYZE. EXPLAIN mostra solo una stima. Se le statistiche sono sbagliate, la stima è sbagliata. EXPLAIN ANALYZE mostra la realtà.
Se dopo aver creato un indice PostgreSQL continua a usare il sequential scan, controlla le statistiche. Esegui VACUUM ANALYZE sulla tabella. L’ottimizzatore non usa l’indice se le statistiche gli dicono che il sequential scan è più veloce.
FAQ — Domande Frequenti
Come faccio a sapere se una query è lenta?
Abilita pg_stat_statements e controlla il tempo medio di esecuzione. Se supera i 100-200 ms per query transazionali, è lenta. Usa anche log_min_duration_statement per loggare tutte le query che superano una soglia.
Qual è la differenza tra EXPLAIN e EXPLAIN ANALYZE?
EXPLAIN mostra il piano stimato da PostgreSQL senza eseguire la query. EXPLAIN ANALYZE esegue la query e mostra il piano reale con i tempi effettivi e le righe processate.
Quando devo usare CREATE INDEX CONCURRENTLY?
Sempre quando crei un indice su un database in produzione. CONCURRENTLY non blocca le scritture sulla tabella. L’indice viene creato in background. Richiede più tempo, ma evita il downtime.
Perché PostgreSQL non usa il mio indice?
Le statistiche potrebbero essere vecchie. Esegui VACUUM ANALYZE sulla tabella. Oppure la query restituisce troppe righe e PostgreSQL ritiene il sequential scan più veloce. Controlla il piano con EXPLAIN ANALYZE.
Conclusioni con takeaway operativi
Per risolvere i problemi di performance di PostgreSQL, segui un metodo. Prima identifica le query lente con pg_stat_statements. Poi analizza il piano di esecuzione con EXPLAIN ANALYZE. Crea gli indici mancanti in modo concorrente. Mantieni le statistiche aggiornate con VACUUM ANALYZE. Regola i parametri di memoria in postgresql.conf.
Non affidarti al tentativo ed errore. Misura i tempi prima e dopo ogni intervento. Un database ottimizzato riduce i costi di infrastruttura e migliora l’esperienza degli utenti.
[Leggi anche: Configurare un VPS Linux da Zero: Checklist 2026]
[Leggi anche: Monitorare Server Linux Performance: 8 Strumenti a Confronto]
[Leggi anche: SSH Senza Password Linux: Guida Completa all’Autenticazione a Chiave Pubblica]
Aggiornato: Aprile 2026