skip to content

questions

6

What are optimizer statistics in a relational database, and what goes wrong when they are missing or out of date?

level: juniorimportance: must knowfreq 58%

answer

  1. Summary of data in the catalog
  2. Row count, NDV, null fraction, MCV, histogram
  3. Planner never reads the data while planning
  4. Wrong estimates → wrong access path / join
  5. Correctness never affected, only speed

basics

~20 s

They are summaries of the data — row counts, distinct values per column, null fractions, common values, value distributions — that the query planner reads to guess how many rows each step will produce. Missing or stale statistics make those guesses wrong, so the planner picks a bad access path or join method and the query runs far slower.

solid answer

~50 s

Optimizer statistics are a compact **summary of what the data looks like**, stored in the system catalog and refreshed by an ANALYZE-style command or a background auto-refresh. Typical contents: table row count and size, and per column the number of distinct values, the fraction of nulls, a list of the most common values with their frequencies, and a histogram of the remaining distribution. The planner never looks at the actual data while planning. It reads these summaries to estimate **how many rows** each predicate and each join will produce, and those row estimates drive every decision: index scan versus sequential scan, which join algorithm, how much memory to request. When statistics are missing or stale, the estimates are wrong. Classic outcomes: a table that grew from 1,000 to 50 million rows since the last refresh is still treated as tiny, so the planner picks a nested loop that now executes 50 million probes. The query is correct — just orders of magnitude slower.

code

sql · 3 lines
sql
COPY events FROM '/data/events.csv';

ANALYZE events;

go deeper

for a junior

Name the main contents — row count, distinct values, nulls, common values, histogram — and say the planner uses them to estimate row counts, so stale ones cause slow but correct queries.

for a middle

Connect specific statistics to specific decisions: MCV to skewed equality predicates, histograms to ranges, row count to join method and memory sizing.

for a senior

Lead with diagnosis — estimated versus actual rows — and with the operational triggers that make statistics go stale, especially bulk loads.

for a principal

Treat statistics freshness as an SLO on plan stability, and reason about refresh cost, sampling rate and skew handling as a fleet-wide policy.

## What statistics are A cost-based optimizer has to compare candidate plans **before** running any of them. To do that it needs to predict how many rows each step will emit. It cannot scan the data to find out — that would cost as much as executing the query. Instead it consults a small, pre-computed summary of the data called *statistics*, kept in the system catalog. A typical statistics set contains: **Per table** - row count (cardinality) - physical size in pages/blocks **Per column** - **NDV** (number of distinct values) — sometimes stored as its reciprocal, the average selectivity of an equality predicate - **null fraction** — what share of rows are NULL - **most-common-value (MCV) list** — the top-k values and their frequencies, capturing skew - **histogram** — buckets summarizing the distribution of everything not in the MCV list - average value width, and often a correlation figure describing how well the column's order matches physical row order **Per index**, additionally, leaf-page counts and tree height. Statistics are gathered by an explicit command (`ANALYZE` in standard-ish spelling, `UPDATE STATISTICS` / `DBMS_STATS` in other engines) and, in every mainstream engine, by a background process that refreshes them automatically after enough rows change. ## How the planner uses them Every estimate begins with the table's row count and is multiplied down by **selectivity** — the fraction of rows a predicate is expected to keep. - `WHERE status = 'X'`: if `status` has 5 distinct values and no skew, selectivity is roughly 1/5. If `'X'` is in the MCV list, its recorded frequency is used directly — far more accurate. - `WHERE created_at > '2026-01-01'`: the histogram tells the planner what share of values sits above that boundary. - `WHERE email IS NULL`: the null fraction gives the answer directly. The resulting row estimate then drives every downstream choice: 1. **Access path.** Estimated 20 rows out of 10 million? Index scan. Estimated 4 million? Sequential scan, because random I/O per row plus table lookups would cost more than reading everything in order. 2. **Join algorithm.** A small outer input favours index nested-loop; two large inputs favour hash or merge. 3. **Join order.** The cheapest order depends on which intermediate results are small. 4. **Memory grants.** Hash tables and sort buffers are sized from estimated row counts; underestimate and the operator spills to disk. ## What breaks when they are wrong The query still returns correct results — statistics never affect correctness. What breaks is performance, sometimes catastrophically: - **Stale row count after growth.** A table that was 1,000 rows at last refresh and is now 50 million still looks tiny. The planner picks a nested loop, which now performs 50 million inner probes instead of a hash join's single pass. - **Stale boundaries after new data.** A histogram ending at last month's maximum date makes `WHERE created_at > yesterday` look like it matches almost nothing, so the planner chooses a plan sized for a handful of rows and gets millions. - **No statistics at all** (fresh table, restored dump not yet analyzed). The planner falls back to hard-coded default selectivities and guesses — occasionally lucky, usually not. - **Under-estimates cause memory spills**; over-estimates cause needless full scans and over-large memory grants that starve other queries. ## The practical habit The things worth internalizing early: 1. After any **bulk load, restore, or large delete**, refresh statistics explicitly rather than waiting for the background job. 2. When a query mysteriously slows down with no code change, compare **estimated versus actual row counts** in the plan; a large divergence points straight at statistics. 3. Statistics are approximate by design — they are usually built from a **sample**, not a full scan, so small errors are normal and expected. It is the order-of-magnitude errors that matter. 4. Statistics are metadata, not data: they never change results, only plans.

  • Do wrong statistics ever produce wrong query results?
    No. Statistics only influence which plan the optimizer chooses; every candidate plan returns the same rows. Bad statistics cost performance — bad access paths, bad join methods, memory spills — but the answer is always correct.
  • Where do statistics come from, and are they exact?
    They are gathered by an ANALYZE-style command or by an automatic background refresh, and they are normally computed from a random sample of rows rather than a full scan, so they are approximate. Row counts and null fractions extrapolate well from a sample; distinct-value counts are the least reliable.
  • How would you tell that a slow query is suffering from bad statistics?
    Compare the estimated row count at each plan step with the actual row count from an instrumented run. A step estimating tens of rows but producing millions — or the reverse — is the signature. That divergence then explains the downstream choice of nested loop versus hash join, or index scan versus sequential scan.

Like a shop's inventory summary rather than the shelves themselves: the manager plans staffing from the summary, and if it says 'we stock about 50 units' when there are 5 million, the plan is wrong even though the shelves are fine.

saying these in an interview costs you the question

  • Thinking statistics affect query results, not just plans
  • Believing statistics are recomputed on every query
  • Assuming statistics are exact rather than sampled approximations
  • Saying an index alone guarantees the index will be used, regardless of statistics
  • Refreshing statistics as a superstitious fix without checking estimated-versus-actual rows

context

open as a page

Which per-column statistics does a cost-based optimizer typically store, and what does a most-common-value list give you that a plain distinct-value count cannot?

level: middleimportance: must knowfreq 48%

basics

~20 s

Typically number of distinct values (NDV), null fraction, a most-common-value list with frequencies, a histogram of the rest, and average value width. NDV alone implies every value is equally common; the MCV list records the actual frequency of skewed values, so a rare and a dominant value are estimated differently.

open as a page

After a nightly bulk load of several million rows, queries that ran in milliseconds yesterday now do full table scans. Explain the mechanism, and how you would fix and prevent it.

level: seniorimportance: must knowfreq 45%

basics

~20 s

The load changed the data but not the catalog statistics, so the optimizer is planning against yesterday's picture — old row counts and a histogram whose maximum value predates the new rows. Estimates collapse, plans flip. Fix by running an ANALYZE-style refresh on the loaded tables; prevent by making that refresh the last step of the load job.

open as a page

Compare equi-width and equi-depth histograms as used by a query optimizer. Which handles skewed data better, and why do engines usually store one alongside a most-common-value list?

level: middleimportance: should knowfreq 36%

basics

~20 s

An equi-width histogram splits the value range into buckets of equal width, so a dense region lands in one huge bucket. An equi-depth (equi-height) histogram splits so every bucket holds about the same number of rows, giving narrow buckets where data is dense. Equi-depth handles skew far better, which is why engines prefer it, with an MCV list handling single dominant values it cannot represent.

open as a page

Optimizer statistics are usually gathered from a sample of rows rather than a full scan. What error does sampling introduce, and which statistic is hardest to estimate accurately from a sample?

level: seniorimportance: nice to knowfreq 26%

basics

~20 s

Sampling makes statistics gathering cheap but approximate. Row counts, null fractions and histogram boundaries extrapolate well from a modest sample. The number of distinct values (NDV) is the hard one: it cannot be reliably extrapolated, and long-tail distributions are systematically under-estimated, which inflates equality selectivity and distorts join estimates.

open as a page

You own a large OLTP database with a mix of small hot tables, huge append-only tables, and nightly-loaded reporting tables. How would you design the statistics-refresh policy across them?

level: principalimportance: nice to knowfreq 22%

basics

~20 s

Treat freshness per table class, not globally. Let automatic refresh handle small hot tables. For huge tables, tighten the change threshold since a percentage-based trigger fires far too late. For loaded tables, refresh explicitly at the end of the load. Raise sampling targets only on skewed, heavily-filtered columns, and monitor staleness as a signal.

open as a page