How to Read an EXPLAIN Query Plan in PostgreSQL

PostgreSQL · Execution plans · DBA-021

How to Read an EXPLAIN Query Plan in PostgreSQL

Understand scan types, row estimates, join operators and the important difference between EXPLAIN and EXPLAIN ANALYZE.

The short answer

Start with plain EXPLAIN to see the plan PostgreSQL expects to use without running the query. Read the plan from the deepest scan nodes upward; check estimated row counts and join methods. Use EXPLAIN (ANALYZE, BUFFERS) only when you intentionally want to execute the statement and measure what happened.

Start with the planned work

Replace the illustrative schema and table with a query from your environment. Plain EXPLAIN produces the estimated plan without performing the underlying table scan.

Plan only · illustrative orders query
EXPLAIN
SELECT order_id, customer_id, order_date
FROM sales.orders
WHERE order_date >= DATE '2026-06-01';

Example objects such as sales.orders must exist for the query to plan successfully.

Read the most important plan signals

Seq ScanPostgreSQL reads table rows sequentially; this can be optimal for small tables or broad result sets.
Index Scan / Bitmap Heap ScanA path that uses an index to locate matching rows or page groups.
Hash Join / Nested Loop / Merge JoinDifferent algorithms for combining inputs; the best one depends on input size and distribution.
cost=… rows=… width=…Planner estimates—not elapsed time, actual rows or bytes read from disk.
actual time / actual rows / loopsMeasurements available only when ANALYZE executes the statement.
BuffersShows block access counts, including cache hits and reads, when requested.

Look for the earliest point where a row estimate differs sharply from the observed row count. A poor estimate can lead to poor choices further up the plan tree.

Measure actual work when it is safe to execute

ANALYZE changes the meaning of the command: the SQL runs. Even a read-only query can consume significant resources on a large production dataset.

Executes the SELECT; use intentionally
EXPLAIN (ANALYZE, BUFFERS)
SELECT order_id, customer_id, order_date
FROM sales.orders
WHERE order_date >= DATE '2026-06-01';

For data-modifying statements, EXPLAIN ANALYZE really performs the change. In a controlled test, the book demonstrates wrapping DML in a transaction and rolling it back. Trigger side effects outside the transaction and sequence changes may still remain, so use a test environment where possible.

A practical plan-reading routine

  1. Observe the innermost scans. What rows enter the plan, and where are filters applied?
  2. Compare estimates with reality. If running ANALYZE, check rows and loops together.
  3. Follow rows upward. Watch for unexpected growth across joins or aggregates.
  4. Look for costly operations. Sorts spilling to disk, heavy nested loops or repeated scans may be clues.
  5. Investigate the reason. Check statistics, predicates and indexing before assuming any scan type is inherently wrong.

Related DBA tasks

DBA-022 looks for index candidates, DBA-024 updates statistics, DBA-026 identifies costly queries over time, and DBA-028 compares estimated and actual work in depth.

Continue learning · Baldwin Apps

More practical solutions in SQL Recipes for DBAs

Find engine-specific recipes for sessions, locks, performance, permissions, backups, maintenance, and troubleshooting. Explore the book and its existing companion materials.

Published

Leave a comment

Your email address will not be published. Required fields are marked *