Overview
The planner (optimizer) turns a SQL statement into a plan: it enumerates candidate scan methods, join orders, and index choices, estimates each one’s cost from table statistics gathered by ANALYZE, and runs the cheapest. Read the plan before adding an index or rewriting SQL, because guesses about slowness are often wrong. When a query suddenly picks a bad plan, suspect the planner’s inputs (stale statistics, skewed data) before the planner.
Rules
EXPLAINshows the estimated plan.EXPLAIN ANALYZEruns the query and adds actual rows, timing, and loops; from PostgreSQL 18 it includesBUFFERSby default, and earlier versions needEXPLAIN (ANALYZE, BUFFERS).- Compare estimated and actual row counts. A large gap means stale statistics (run
ANALYZE; see vacuum) or correlated columns that needCREATE STATISTICS. - Key nodes: Seq Scan, Index Scan, Index Only Scan (needs a covering index and a current visibility map), Bitmap Heap Scan, and Nested Loop, Hash, and Merge joins.
- Planner switches such as
enable_seqscan = offare session-level debugging tools, never a production fix for a missing index (sequential-scan). - Many
shared readbuffers mean the data came from outsideshared_buffers. A fast plan executed hundreds of times per request is a different problem: n-plus-one-query.
EXPLAIN (ANALYZE, BUFFERS)
SELECT id, title FROM articles
WHERE author_id = 42 AND published_at > now() - interval '30 days'
ORDER BY published_at DESC;
-- A Seq Scan on a large table: try an index on (author_id, published_at DESC), then re-run.Related
- btree-index: the usual fix for an unwanted Seq Scan.
- sequential-scan: the fallback scan.
- gin-index: indexes for composite values.
- vacuum: keeps statistics current.
- postgres-explain: reading plans in depth.
- postgres-explain: node annotations.