EXPLAIN shows the plan PostgreSQL intends to use. EXPLAIN ANALYZE actually runs the query and shows what happened. Together they are the fastest route from "this is slow" to "this is why".
The command you'll use 90% of the time
EXPLAIN (ANALYZE, BUFFERS)
SELECT o.id, o.total
FROM orders o
WHERE o.customer_id = 42
AND o.created_at >= now() - interval '30 days';
ANALYZEexecutes the query and adds actual timings and row counts.BUFFERSshows how many 8 kB pages came from shared buffers (hit) versus from disk or the OS cache (read). Since PostgreSQL 18,BUFFERSis included automatically withANALYZE.
⚠️
EXPLAIN ANALYZEreally runs the statement. ForINSERT,UPDATEorDELETE, wrap it in a transaction and roll back:BEGIN; EXPLAIN ANALYZE UPDATE orders SET status = 'x' WHERE id = 1; ROLLBACK;
Anatomy of a plan node
Index Scan using orders_customer_id_idx on orders o
(cost=0.43..812.50 rows=210 width=16)
(actual time=0.041..3.912 rows=187 loops=1)
Index Cond: (customer_id = 42)
Filter: (created_at >= (now() - '30 days'::interval))
Rows Removed by Filter: 1604
Buffers: shared hit=1650
- cost — startup..total cost in the planner's arbitrary units.
