Database

EXPLAIN Plan Analyser

Read a PostgreSQL plan with the per-loop row counts multiplied out, each node's own time separated from its children's, and the usual culprits flagged.

Plan

How to produce a plan worth reading

EXPLAIN (ANALYZE, BUFFERS) <your query>;

ANALYZE runs the query, which is the only way to see an estimate that was wrong or a sort that spilled. It also means a write statement will actually write — wrap one in a transaction and roll back.

Summary
Nodes
7
Mode
EXPLAIN ANALYZE
Planning
0.412 ms
Execution
284.2 ms
Costliest node
Seq Scan78%
What to look at
  • InfoSeq Scan on orders o accounts for 78% of the execution time on its own. A child's time is included in its parent's, so this is the figure to look at rather than the largest number in the plan.
  • HighSeq Scan on orders o read 500.0k rows and discarded 480.9k of them. An index on the filtered column would let PostgreSQL skip that work: Filter: ((created_at >= (now() - '30 days'::interval)) AND (status = 'completed'::text)).
  • HighSeq Scan on customers c read 50.0k rows and discarded 45.2k of them. An index on the filtered column would let PostgreSQL skip that work: Filter: (tier = ANY ('{gold,platinum}'::text[])).
Nodes
rows and time multiplied out by loops
NodeEst. rowsActualLoopsOwn timeShare
Limit505010.007 ms0%
Sort4.7k5095×10.921 ms0%
HashAggregate4.7k4.6k120.3 ms7%
Hash Join19.0k19.1k123.4 ms8%
Seq Scan on orders o19.0k19.1k1221.5 ms78%
Hash4.8k4.8k11.7 ms1%
Seq Scan on customers c4.8k4.8k116.4 ms6%

Node detail

Sort

  • Sort Key: (sum(o.total_cents)) DESC
  • Sort Method: top-N heapsort Memory: 32kB

HashAggregate

  • Group Key: c.id, c.email, c.tier
  • Batches: 1 Memory Usage: 1553kB

Hash Join

  • Hash Cond: (o.customer_id = c.id)

Seq Scan on orders o

  • Filter: ((created_at >= (now() - '30 days'::interval)) AND (status = 'completed'::text))
  • Rows Removed by Filter: 480896

Hash

  • Buckets: 8192 Batches: 1 Memory Usage: 379kB

Seq Scan on customers c

  • Filter: (tier = ANY ('{gold,platinum}'::text[]))
  • Rows Removed by Filter: 45236

“Own time” is this node's time with its children's subtracted, which is what identifies the expensive step — a parent's reported time always includes everything beneath it.