Unit 6 · Databases · Lesson 7 of 10
Query optimization and cardinality estimation
Step 1: Learn the ideas
- Logical rewrites: predicate pushdown, projection pruning, subquery unnesting
- Statistics: row counts, histograms, most-common values, distinct counts
- Selectivity estimation under uniformity and independence assumptions
- Join cardinality estimation and error propagation
- Cost models: I/O, CPU and memory terms
- Join enumeration: System R dynamic programming, left-deep versus bushy, Cascades
- Heuristic and randomized search for many-way joins
- Reading EXPLAIN and EXPLAIN ANALYZE
- Correlated columns and extended statistics
- Skew and parameter-sensitive plans
Why it matters
The same SQL can run in milliseconds or hours depending on join order and algorithm choice. Optimizers make that choice from estimates, and estimates are wrong in predictable ways. Pointing at the plan node where an estimate went wrong, and explaining why, is a concrete debugging skill and the core problem of a long line of optimizer research.
Core ideas
Architecture. Parse and rewrite the query (push filters down, unnest subqueries, prune columns), enumerate physical plans, estimate the cardinality of every intermediate result, cost each plan and keep the cheapest. Cardinality estimates feed the cost model, so a perfect cost model with bad estimates still picks bad plans.
Selectivity. Systems keep per-column statistics: row count, number of distinct values, most common values with their frequencies, and a histogram for the rest (PostgreSQL uses equal-population buckets). An equality predicate on a common value uses its recorded frequency; otherwise roughly the remaining frequency divided by the remaining distinct values. A range predicate uses the fraction of histogram buckets it covers. Conjunctions multiply selectivities, assuming independence, which underestimates when columns are correlated (city = 'Zürich' AND country = 'CH').
Joins. For an equi-join on columns with d1 and d2 distinct values, a standard estimate is |R|·|S| / max(d1, d2), assuming every value on the side with fewer distinct values finds a match. Each join's error multiplies into the next, so estimates for five-way joins can be off by orders of magnitude, usually downward. Underestimates are the dangerous direction: they make nested-loop joins and small hash tables look cheap.
Search. System R-style dynamic programming builds the best plan for each subset of relations from the best plans of its smaller subsets, keeping extra plans whose sort order is useful later (interesting orders). The search space grows exponentially, so systems restrict tree shapes, avoid cross products, or switch to heuristics beyond a number of tables (PostgreSQL's genetic optimizer, controlled by geqo_threshold). Cascades-style optimizers search top-down with memoization and transformation rules.
Reading plans. In PostgreSQL, compare estimated rows with actual rows (per loop, times the number of loops), working bottom-up. The lowest node where they diverge sharply is usually the cause; nodes above it are victims. Typical fixes: run ANALYZE, raise the statistics target for a skewed column, create extended statistics for correlated columns, rewrite a predicate the estimator cannot see through, or add an index that makes a robust plan available.
Common misconceptions
- "The optimizer finds the optimal plan." It finds the cheapest plan according to its estimates and cost model, within a restricted search space.
- "Faster hardware fixes bad plans." A plan that is wrong by orders of magnitude in rows stays wrong.
- "Start with the most expensive node." Start with the lowest node where estimates and actuals diverge; expensive nodes above it are consequences.
- "Statistics are always current." They are sampled and refreshed periodically; bulk loads and shifting skew leave them stale.
- "Hints are the fix." Hints freeze one plan for today's data; fixing statistics or the query shape usually lasts longer.
How to approach it
Learn the estimation formulas on a small example, then work with real plans. Load data with real correlations (the IMDB data behind the Join Order Benchmark works well) into PostgreSQL, run EXPLAIN ANALYZE on multi-join queries and annotate the error at each node. Add extended statistics and see which estimates change. In interviews, walk through the whole loop: symptom, plan, first misestimate, cause, fix, verification.
Suggested order
- 1Read CMU 15-445 Fall 2025 Lecture #15 notes (Query Planning & Optimization I) for rewrites, bottom-up System R enumeration and top-down Volcano-style search.
- 2Read Lecture #16 notes (Query Planning & Optimization II) for statistics, histograms, sampling and cost estimation.
- 3Read the PostgreSQL pages Using EXPLAIN and Statistics Used by the Planner, then Row Estimation Examples to see the selectivity arithmetic on real statistics.
- 4Read Multivariate Statistics Examples and try CREATE STATISTICS on two correlated columns.
- 5Read Leis et al. (How Good Are Query Optimizers, Really?) with its paper guide, at least the sections on estimation and on when bad estimates hurt.
- 6Do the Join Order Benchmark EXPLAIN ANALYZE exercise and annotate three plans.
Paper guides: How Good Are Query Optimizers, Really?
Step 2: Check off what you can do
Tap each one you can genuinely do or explain. Each earns 10 XP.
Step 3: Go deeper
PostgreSQL documentation: Transaction IsolationStart here
PostgreSQL Global Development GroupSection: Using EXPLAIN
How to read PostgreSQL plans, costs, estimated versus actual rows, loops and buffers.
Open(opens in a new tab)Link responded, 5 Oct 2026PostgreSQL documentation (current release)
PostgreSQL Global Development GroupSection: Row Estimation Examples
Step-by-step selectivity calculations from most-common values, histograms and distinct counts, including joins.
Open(opens in a new tab)Link responded, 5 Oct 2026CMU 15-445/645 Intro to Database SystemsStart here
Carnegie Mellon University Database GroupSection: Fall 2025 Lecture #16: Query Planning & Optimization II (notes)
Statistics, histograms, sampling and cost estimation; Lecture #15 covers rewrites and plan enumeration.
Open(opens in a new tab)Link responded, 5 Oct 2026Berkeley CS186 Database Systems course notes
UC Berkeley CS186Section: Note 10: Query Optimization
Worked selectivity estimates and System R-style dynamic programming with interesting orders.
Open(opens in a new tab)Link responded, 5 Oct 2026How Good Are Query Optimizers, Really?
PVLDB (VLDB Endowment)Evidence on how estimation errors, cost models and plan search each affect real query performance.
Open(opens in a new tab)Link responded, 5 Oct 2026
Step 4: Practice
Load the JOB data into PostgreSQL, run ANALYZE, and for five multi-join queries record estimated and actual rows at each node in a table. Identify the lowest node with a tenfold or larger error and explain the cause (correlation, join containment, skew). Try one fix per query (extended statistics, a rewrite, an index) and report whether the plan and runtime changed. BusTub Project #3 adds the implementation side, including an optimizer rule that rewrites nested-loop joins into hash joins.
- Find cardinality misestimates with EXPLAIN ANALYZE on the Join Order BenchmarkOpen(opens in a new tab)Not attemptedJoin Order Benchmark + local PostgreSQL
- CMU 15-445 Fall 2025 Project #3: Query Execution (BusTub)Open(opens in a new tab)Not attemptedCMU 15-445
Record attempts on each problem’s page (+15 XP, +10 more when solved on your own).
Step 5: Test yourself
Answer each one out loud or on paper before you finish the lesson.
- Question 1Why does the independence assumption usually underestimate conjunctive predicates on correlated columns?
- Question 2Why are underestimates more dangerous than overestimates for join algorithm choice?
- Question 3What does “actual rows” mean for a node executed in a loop, and how do you compare it with the estimate?
- Question 4What do interesting orders add to dynamic programming over join orders?
- Question 5When would an optimizer switch from exhaustive search to heuristics?
- Question 6What would you check first if a query became slow after a bulk load?
You’re done when you can
- Annotate three real EXPLAIN ANALYZE plans with estimation errors and name the first bad estimate in each.
- Compute selectivities for an equality, a range and a two-column conjunctive predicate from given statistics.
- Explain System R-style join enumeration and interesting orders on a four-table example.
- Fix at least one misestimate with extended statistics or a rewrite and show the plan change.
Finish the lesson
Unlocks next: Query optimization research and learned components


