EXPLAIN (ANALYZE, BUFFERS) în PostgreSQL

Indecși, performanță și terminalul psql PostgreSQL 9.0+ (GENERIC_PLAN / MEMORY din PostgreSQL 16–17)

Afișează planul de execuție al optimizatorului, timpii reali pe fiecare nod și blocurile de memorie citite.

Sintaxă SQL
EXPLAIN (ANALYZE, BUFFERS, TIMING, FORMAT TEXT) SELECT ...;

Ce face EXPLAIN (ANALYZE, BUFFERS)

Comanda EXPLAIN afișează planul de execuție pe care optimizatorul bazat pe costuri (query planner) din PostgreSQL îl alege pentru o interogare: cum sunt scanate tabelele (Seq Scan, Index Scan, Index Only Scan, Bitmap Heap Scan) și ce algoritm de joncțiune este folosit (Nested Loop, Hash Join, Merge Join).

Când adaugi opțiunea ANALYZE, PostgreSQL execută efectiv interogarea și afișează alături de estimări și valorile reale (actual time, rows, loops). Împreună cu BUFFERS (care arată câte pagini de 8 KB au fost găsite în memoria RAM cache — shared hit — față de cele citite de pe disc — read), EXPLAIN (ANALYZE, BUFFERS) este instrumentul suprem de diagnosticare a interogărilor lente.

Clauze, operatori și argumente

Clauză / Operator Descriere
EXPLAIN interogare Afișează doar planul estimat și costurile calculate din statistici, fără a rula interogarea.
ANALYZE Execută interogarea și măsoară timpul real (în milisecunde) și numărul real de rânduri la fiecare pas.
BUFFERS Arată numărul de blocuri de 8 KB citite din memoria partajată (shared hit) sau de pe disc (shared read).
VERBOSE Afișează lista completă de coloane ieșite din fiecare nod al planului și numele complet al obiectelor.
SETTINGS În PostgreSQL 12+, afișează parametrii de configurare non-default care au influențat planificatorul.
FORMAT TEXT | JSON | YAML Specifică formatul de ieșire al planului (FORMAT JSON este folosit de vizualizatoare grafice).

Exemple

Exemplul 1: Inspectarea unei interogări indexate cu EXPLAIN (ANALYZE, BUFFERS)

Verificăm că interogarea folosește indexul pe (client_id, plasata_la) și că toate paginile vin din memoria RAM (shared hit=4).

EXPLAIN (ANALYZE, BUFFERS)
SELECT id, total_lei, plasata_la
FROM comenzi
WHERE client_id = 101
ORDER BY plasata_la DESC
LIMIT 5;

Rezultat interogare

                                                                QUERY PLAN                                                                
------------------------------------------------------------------------------------------------------------------------------------------
 Limit  (cost=0.42..8.45 rows=5 width=20) (actual time=0.031..0.036 rows=3 loops=1)
   Buffers: shared hit=4
   ->  Index Scan Backward using idx_comenzi_client_data on comenzi  (cost=0.42..24.50 rows=15 width=20) (actual time=0.029..0.034 rows=3 loops=1)
         Index Cond: (client_id = 101)
         Buffers: shared hit=4
 Planning Time: 0.115 ms
 Execution Time: 0.058 ms
(7 rows)

Exemplul 2: Testarea în siguranță a unui UPDATE sau DELETE cu EXPLAIN ANALYZE în interiorul unui ROLLBACK

Deoarece EXPLAIN ANALYZE chiar execută comanda, încadrăm un DELETE într-o tranzacție pe care o anulăm cu ROLLBACK.

BEGIN;

EXPLAIN (ANALYZE, BUFFERS)
DELETE FROM sesiuni_active
WHERE expira_la < NOW();

ROLLBACK;

Rezultat interogare

BEGIN
                                                          QUERY PLAN                                                          
------------------------------------------------------------------------------------------------------------------------------
 Delete on sesiuni_active  (cost=0.29..18.40 rows=120 width=6) (actual time=0.412..0.413 rows=114 loops=1)
   Buffers: shared hit=92
   ->  Index Scan using idx_sesiuni_expira on sesiuni_active  (cost=0.29..18.40 rows=120 width=6) (actual time=0.025..0.148 rows=114 loops=1)
         Index Cond: (expira_la < now())
         Buffers: shared hit=6
 Planning Time: 0.084 ms
 Execution Time: 0.445 ms
(7 rows)

ROLLBACK

Greșeli frecvente

  • Rularea EXPLAIN ANALYZE DELETE ... sau EXPLAIN ANALYZE UPDATE ... direct în afara unei tranzacții: interogarea chiar va șterge sau modifica rândurile! Pune-o întotdeauna între BEGIN; și ROLLBACK;.
  • Ignorarea multiplicatorului loops=N: timpul actual time=0.05..0.12 afișat pe un nod este media per iterație; dacă pe acel nod apare loops=1000, timpul total petrecut acolo este 0.12 * 1000 = 120 ms.

Comenzi și funcții înrudite