Troubleshooting

EXPLAIN Plan Review Checklist

A practical checklist for reviewing PostgreSQL EXPLAIN ANALYZE BUFFERS output: row estimates, loops, filters, buffers, temp files, joins, and sort methods.

Always start with real execution when safe

Plain EXPLAIN is a prediction. EXPLAIN (ANALYZE, BUFFERS) shows what the query actually did. Use real parameters and a representative environment whenever possible.

EXPLAIN (ANALYZE, BUFFERS)
SELECT o.id, o.created_at, c.email
FROM orders o
JOIN customers c ON c.id = o.customer_id
WHERE o.tenant_id = $1
  AND o.created_at >= now() - interval '7 days'
ORDER BY o.created_at DESC
LIMIT 100;

Checklist

  • Find the slow branch: read from the bottom up and follow actual time plus loops.
  • Compare estimated rows with actual rows: large gaps usually explain bad join order or index choice.
  • Watch loops: a cheap node can become expensive when it runs hundreds of thousands of times.
  • Look for rows removed by filter: this can mean the index found too broad a candidate set.
  • Read buffers: high shared reads suggest disk or cache pressure; high shared hits can still mean CPU-heavy page walking.
  • Check temp activity: external sorts and multi-batch hashes point to spill risk.
  • Check sort method: an external merge sort in a request path is usually worth investigating.

What row estimate errors usually mean

SymptomCommon causeNext check
Estimate far below actualStale stats or correlated predicatesRun ANALYZE, inspect extended statistics
Estimate far above actualFilter more selective than planner knowsCheck stats target and predicate shape
Nested loop runs too many timesOuter side underestimatedFind first upstream estimate error
Sort spills to diskLarge input or low memoryReduce input before raising work_mem

Plan review habit

Do not jump straight from "Seq Scan" to "add index." A sequential scan on a small or broad result can be correct. The job is to understand whether PostgreSQL read more data than the query should require, and why.