Lesson 14 · Query performance
The planner & statistics
How Postgres decides how to run the query you only described.
Your win: explain how the planner chooses a plan, where its cost estimates come from, and why stale statistics are a classic cause of a query suddenly going slow.
You say what; the planner decides how
SQL is declarative (Lesson 1). For any query there are many ways to execute it — which scan, which index, which join method, which order. The planner (optimizer) enumerates candidate plans and picks the one with the lowest estimated cost.1
Cost comes from statistics
To estimate cost, the planner must guess selectivity — what
fraction of rows a WHERE condition will match. It reads
statistics from the pg_statistic catalog (readable
via the pg_stats view): row counts, number of distinct values, most-common
values, and histograms.2
ANALYZE (and by autovacuum, Lesson 9). If they're
stale — e.g. right after a bulk load — the planner misestimates selectivity and can pick a
terrible plan (a seq scan where an index would win, or vice versa). "Run
ANALYZE" fixes a surprising number of "suddenly slow" queries.
resource_path is
highly selective (one tenant is a small slice of the table), so the planner reliably
favours the resource_path index that RLS makes every query filter on. Costs
are in arbitrary units (relative to a sequential page read = 1) — it's the comparison
that matters, not the absolute number.
PostgreSQL — Statistics Used by the Planner
How pg_statistic/pg_stats drive selectivity estimates, and
extended (multivariate) statistics for correlated columns.
Check yourself (from memory)
Q1. The planner chooses a plan by…
Q2. Cost estimates come from…
pg_stats.
Q3. Statistics are refreshed by…
ANALYZE (manual) and autovacuum (automatic).
Stale stats → misestimates → bad plans.
pg_statistic (via pg_stats): row counts,
distinct values, most-common values, histograms — used to estimate a WHERE clause's
selectivity. Stats are refreshed by ANALYZE/autovacuum; stale stats →
misestimated selectivity → a bad plan (a classic cause of a query going slow after a bulk
load).SELECT * FROM pg_stats WHERE tablename = 'emails'
— or learn extended statistics for correlated columns? Ask me.