PostgreSQL Mastery
PostgreSQL Mastery lessons / Module 18: Planner statistics

Planner statistics and misestimates

Level 4: Performance25 min

The planner never looks at your data while planning; it reads statistics gathered by ANALYZE. Most bad plans trace back to a row estimate that is wrong because the statistics are stale, too coarse, or blind to correlation between columns.

This lesson is interactive. Run every example on a real PostgreSQL 18 server in your browser, read EXPLAIN ANALYZE plans, and take the quiz.
Open the interactive lesson
You will learn to
  • Read pg_stats: null_frac, n_distinct, most_common_vals, histogram_bounds, correlation
  • Fix stale statistics and raise the statistics target for skewed columns
  • Use CREATE STATISTICS for correlated columns

conceptThe concept

ANALYZE samples about 300 times default_statistics_target (100) rows per table and stores, per column, the fraction of NULLs, the number of distinct values, the most common values with their frequencies, a histogram of the rest, and the physical correlation. The planner multiplies selectivities to estimate rows for each predicate.

Multiplying assumes columns are independent. city = 'London' AND country = 'UK' is estimated as P(London) times P(UK), far too low, because every London row is in the UK. CREATE STATISTICS ... (dependencies) teaches the planner functional dependencies; (ndistinct) fixes GROUP BY estimates on column combinations; (mcv) stores common value combinations.

Autovacuum runs ANALYZE after about 10% of a table changes (autovacuum_analyze_scale_factor). After a bulk load, run ANALYZE yourself. For skewed columns, ALTER TABLE ... ALTER COLUMN ... SET STATISTICS 1000 keeps more common values.

whyWhy it matters

An estimate off by 100x changes join algorithms and join order. Fixing statistics fixes the plan for every query that uses those columns, without rewriting any of them.

Use caseReal-world scenario

A marketing segment query filters by city and country together and the planner badly underestimates the rows, choosing a nested loop. One CREATE STATISTICS later, the estimate is exact.

Try itExamples

Run and edit these in the interactive lesson.

What ANALYZE knows about a column
\x
SELECT attname, null_frac, n_distinct, most_common_vals, most_common_freqs, correlation
FROM pg_stats
WHERE tablename = 'orders' AND attname = 'status';
\x
Correlated columns are underestimated
EXPLAIN ANALYZE
SELECT * FROM customers WHERE city = 'London' AND country = 'UK';
Estimated 28, actual 164: the planner multiplied the two selectivities as if city and country were independent.
Extended statistics fix it
BEGIN;
CREATE STATISTICS customers_city_country (dependencies) ON city, country FROM customers;
ANALYZE customers;
EXPLAIN SELECT * FROM customers WHERE city = 'London' AND country = 'UK';
ROLLBACK;

The course database includes pgm.estimate(query), which returns the planner's row estimate. It is handy for comparing estimates without reading whole plans.

Using the estimate helper
SELECT pgm.estimate($$SELECT * FROM customers WHERE city = 'London' AND country = 'UK'$$) AS estimated,
       (SELECT count(*) FROM customers WHERE city = 'London' AND country = 'UK') AS actual;

internalsUnder the hood

Statistics live in pg_statistic (readable via the pg_stats view) and extended statistics in pg_statistic_ext_data. Since 18, pg_upgrade carries statistics over to the new cluster, so the first hours after an upgrade no longer run on empty statistics.

Avoid / preferDo this, not that

Avoid
ALTER SYSTEM SET default_statistics_target = 10000;
Prefer
ALTER TABLE orders ALTER COLUMN customer_id SET STATISTICS 1000;
ANALYZE orders;

A huge global target slows ANALYZE and planning for every table. Raise it only for the skewed columns that need it.

Avoid
-- bulk load 50 million rows, then run reports immediately
Prefer
COPY big_table FROM ...;
ANALYZE big_table;

Until autovacuum analyzes the table, the planner works from stale or empty statistics and can choose terrible plans.

PitfallsCommon mistakes

  • Forgetting to ANALYZE after a bulk load or a big DELETE, then debugging the "mysterious" bad plan.
  • Raising default_statistics_target globally to 10000. ANALYZE and planning both get slower; raise it per column where skew matters.
  • Creating extended statistics and not running ANALYZE afterwards; they stay empty until then.

LimitsWhen not to use it

Do not create extended statistics for every column pair. Add them where EXPLAIN ANALYZE shows estimates off by an order of magnitude on correlated filters.

SecuritySecurity notes

pg_stats shows most common values and histograms, which are samples of real data, but only for columns the current role can read. The underlying pg_statistic catalog is superuser-only.

ChallengeTry it yourself

Compare the planner's estimate with the real number of orders for customer 1.

Show one solution
One solution
SELECT pgm.estimate('SELECT * FROM orders WHERE customer_id = 1') AS estimated,
       (SELECT count(*) FROM orders WHERE customer_id = 1) AS actual;

InterviewQuestions you should be able to answer

Why might the planner badly underestimate rows for a query with two equality predicates?
It assumes the predicates are independent and multiplies their selectivities. When the columns are correlated (city and country, model and make), the real fraction is much higher than the product. CREATE STATISTICS with dependencies or mcv, followed by ANALYZE, lets the planner account for the correlation.
What does pg_stats tell you and how do you use it?
For each column: null_frac, avg_width, n_distinct (negative means a fraction of rows), most_common_vals with most_common_freqs, histogram_bounds for the remaining values, and correlation with physical order. Compare these with reality when estimates are off: missing values in the MCV list for a skewed column suggest raising its statistics target; correlation near ±1 suggests BRIN or cheap range scans.

QuizCheck your understanding

4 questions with instant feedback in the interactive lesson; a wrong answer goes into your review deck.

Go deeperOfficial documentation