Skip to content

Understand plan findings

EXPLAIN findings name specific anti-patterns in the plan tree; they are the input to candidate generation, not a diagnosis on their own.

Finding kinds

Finding Fires when
seq_scan A sequential scan node appears in the plan
high_cost A node's planner cost is unusually high relative to the plan
cardinality_misestimate Estimated rows disagree sharply with actual rows (ANALYZE only)
low_selectivity ≥ 90% of scanned rows are discarded, with ≥ 1,000 rows scanned
sort_spill A sort node's space type is Disk (work_mem exceeded)
hash_batches A hash node used more than one batch
loop_inflation A node ran ≥ 1,000 loops (e.g. a nested-loop inner side)
parallel_shortage Fewer parallel workers launched than planned
partition_pruning ≥ 3 child subplans, with none removed by pruning
buffer_pressure ≥ 1,000 blocks read, with ≥ 50% of reads from disk (ANALYZE + BUFFERS)
stale_statistics Row estimates suggest ANALYZE has not run recently
index_candidate A scan shape suggests an index would help
index_health An existing index looks unused, duplicated, or only partially covering

Most of these except cardinality_misestimate and buffer_pressure are visible on a plain EXPLAIN; the ones tied to actual timings and buffers need EXPLAIN ANALYZE.

Estimated versus actual

Plain EXPLAIN EXPLAIN ANALYZE
Executes the query No Yes
Rows Planner's estimate Estimate and actual
Cost Planner cost (arbitrary units) Same, ANALYZE does not change cost
Timing None planning_time_ms, server_execution_time_ms
evidence_mode estimated observed (once a non-zero execution time is reported)

Planner cost is never a time. It is in arbitrary, engine-internal units and is not proportional to wall-clock duration; two plans with a 10× cost difference can run in nearly the same time, or the reverse. Only EXPLAIN ANALYZE measures anything, and one run is one sample, see Compare plans for repeated timing.

Partitions and pruning

On the demo's partitioned demo.sales, a predicate the planner can range-bound (date >= … AND date < …) prunes to the relevant monthly partitions; a function-wrapped predicate (DATE_TRUNC('month', date) = …) usually cannot, so the planner scans every partition and partition_pruning fires. This is the shape Suggest and rank candidates targets first.

Buffers

Buffers: shared hit=… read=… (under EXPLAIN (ANALYZE, BUFFERS)) distinguishes a plan served from cache (hit) from one that reads from disk (read). A lower planner cost with more disk reads is not automatically a win; it depends on cache state, which changes between runs.

Index advice

index_candidate and index_health findings feed IndexAdvice: suggested DDL, labelled for review only, never applied automatically. See Suggest and rank candidates.

See also

Investigate a slow query · Compare plans · Evidence and status vocabulary