skip to content

questions

22

Relational database optimizers are commonly described as either rule-based or cost-based. Explain the difference, and why essentially every modern relational engine chose the cost-based design.

level: juniorimportance: must knowfreq 52%

answer

  1. Rule-based: fixed priority list, blind to data
  2. Cost-based: enumerate → estimate cardinality → cost → pick cheapest
  3. System R, 1979
  4. Selectivity decides index vs scan — only cost can see it
  5. Price of cost-based: estimation error + plan instability

basics

~20 s

A rule-based optimizer applies a fixed priority list of heuristics regardless of the data. A cost-based optimizer enumerates alternative plans, estimates the cost of each from statistics about the data, and picks the cheapest. Cost-based wins because the right plan depends on data distribution, which rules cannot see.

solid answer

~60 s

A **rule-based optimizer** transforms a query using a fixed ranking of heuristics — "prefer an index over a full scan", "drive from this table first" — with no knowledge of how much data is involved. Its output is deterministic and predictable, and it is wrong whenever the data contradicts the ranking: using an index to fetch 80% of a table is far slower than scanning it. A **cost-based optimizer** works in three parts: enumerate candidate plans (access paths, join orders, join and grouping algorithms); estimate each plan's **cardinality** — how many rows flow between operators — from statistics; and convert those cardinalities into an abstract **cost** using a model of I/O and CPU. The cheapest plan wins. Modern engines are cost-based because the correct plan genuinely depends on the data: on selectivity, on table sizes, on how much is cached. Rules cannot express "this predicate matches 3 rows here and 3 million there". The trade-off is that cost-based plans are only as good as their estimates, and they can change under you when statistics or parameters change — hence plan instability, and features like hints and plan baselines to pin behaviour.

go deeper

for a junior

State the core contrast — fixed heuristics versus estimating and comparing plan costs from data statistics — and give the index-versus-scan example.

for a middle

Break the cost-based optimizer into enumeration, cardinality estimation, and the cost model, and note that cost is an internal comparison unit.

for a senior

Emphasize that estimation error, not the cost formula, causes most bad plans, and discuss plan stability tooling and when pinning a plan is justified.

for a principal

Frame the trade as adaptivity versus predictability — cost-based buys data-dependent plans at the price of plan volatility — and describe the governance needed: statistics maintenance, regression detection, and a policy for pinned plans.

## What an optimizer is doing A SQL query says *what* result you want, never *how* to produce it. Between parsing and execution, the optimizer must choose a physical plan: which access path reaches each table (full scan, index scan, index seek plus row lookup), which algorithm implements each join, in what order tables are joined, and how grouping and sorting are performed. For a query joining six tables there are millions of valid plans whose runtimes differ by orders of magnitude. Choosing among them is the optimizer's whole job. ## Rule-based optimization The original approach — Oracle's RBO is the canonical example, and it was still in use into the 1990s — ranks access paths and transformations in a fixed priority list. A simplified version: single-row lookup by ROWID beats a unique index lookup, which beats a non-unique index range scan, which beats a full table scan; join order follows the order tables appear in the query text. Properties: - **Deterministic.** The same SQL always yields the same plan. No statistics, so no surprise regression after a statistics refresh. - **Cheap to compile.** No enumeration, no estimation. - **Blind to data.** This is the fatal defect. "Prefer the index" is right for a predicate matching 5 rows out of 10 million and badly wrong for one matching 8 million, where the index path means millions of random row fetches while a sequential scan reads the table efficiently. A rule-based optimizer cannot tell those apart, because the distinguishing fact — selectivity — lives in the data, not the query text. - **Syntax-sensitive.** Because join order follows the text, rewriting the FROM clause changes performance, which is a terrible property for maintainable SQL. ## Cost-based optimization System R (IBM, 1979) established the design every mainstream engine still uses. Three components: **1. Plan enumeration.** Generate candidate plans by applying algebraic equivalences (predicate pushdown, join commutativity and associativity, subquery flattening) and by choosing physical operators. The space is exponential, so the search is pruned — dynamic programming over join orders, interesting-order tracking, and heuristic or randomized search once the table count is large. **2. Cardinality estimation.** For each intermediate result, estimate how many rows it produces, starting from stored statistics (row counts, distinct-value counts, histograms, null fractions) and applying selectivity formulas per predicate. This is the part that decides plan quality. **3. Cost model.** Turn estimated cardinalities and access patterns into a number: pages read (distinguishing sequential from random), rows and comparisons processed, memory required. The unit is arbitrary and internal — it is a *comparator*, not a prediction of milliseconds. The cheapest plan is executed. ## Why cost-based won Because the right answer is data-dependent, and only a cost model can express that. The same query against a 1,000-row table and a 1-billion-row table deserves different plans. The same predicate deserves an index seek for a rare value and a scan for a common one. A cost-based optimizer also lets the engine add new physical operators — hash join, bitmap access, parallel execution — and have them chosen automatically wherever they win, instead of demanding a new rule and a new priority position. ## What it costs you - **Estimates can be wrong.** The optimizer optimizes its *belief* about the data. A cardinality estimate off by 1000× yields a plan that is optimal for a world that does not exist. Most catastrophic plans trace to estimation error, not to the cost formula. - **Plans change under you.** New statistics, changed data, a different parameter value, or an engine upgrade can flip a plan. When the flip goes the wrong way it looks like a spontaneous outage. This drove the whole ecosystem of hints, plan baselines, plan guides, and forced plans. - **Compilation is not free.** Enumeration costs CPU, which is why engines cache plans and why plan-cache behaviour (and parameter-sensitive plans) becomes its own topic. - **The model is a simplification.** Cost formulas assume things — a fixed sequential-to-random I/O ratio, an assumed cache-hit fraction, independent predicates — that are approximations of a real machine. ## The nuance worth stating "Cost-based" does not mean rule-free. Every real optimizer still applies heuristic rewrites unconditionally when they are always-or-almost-always beneficial — pushing predicates below joins, eliminating provably empty branches, flattening simple subqueries — because costing them out would waste compile time. The accurate description is that modern optimizers are **cost-based with a heuristic rewrite layer**, and the cost model decides only where the answer genuinely depends on data.

  • Give a concrete case where the rule "always prefer an index over a full table scan" is badly wrong.
    A predicate matching a large fraction of a heap table. Reaching 8 million of 10 million rows through a non-covering index means walking the index and then doing millions of largely random row fetches, often revisiting the same page many times. A sequential scan reads each page once in physical order with readahead, so it can be an order of magnitude faster. The crossover typically arrives at a fairly low selectivity — often single-digit percentages — precisely because of the random-versus-sequential I/O difference.
  • If cost-based optimization is better, why do engines still ship hints, plan baselines, and forced plans?
    Because a cost-based plan is only as good as its cardinality estimates, and estimates fail on correlated columns, opaque predicates, and skewed parameters. Those mechanisms let an operator pin a known-good plan when the optimizer's belief about the data is systematically wrong. They are a stability tool of last resort: they freeze the plan, so they also freeze out improvements as the data changes, and they need review whenever data or the engine version shifts.

Rule-based is a driver who always takes the highway; cost-based is a navigation app that checks live traffic. The app is usually better, and it is spectacularly wrong when its traffic data is stale.

saying these in an interview costs you the question

  • Believing cost is measured in milliseconds rather than in arbitrary internal units used only for comparison
  • Claiming modern optimizers use no heuristics at all — unconditional rewrites still exist alongside costing
  • Saying a cost-based optimizer picks the fastest plan, rather than the cheapest plan under its estimates
  • Assuming an index is always preferable to a scan regardless of selectivity
  • Thinking join order is determined by the order tables appear in the query text in a cost-based engine

context

open as a page

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%

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.

open as a page

A cost-based query optimizer must decide whether to reach a table through an index or read it whole. Explain how an estimated selectivity, derived from stored statistics, drives that decision.

level: middleimportance: must knowfreq 66%

basics

~20 s

The optimizer estimates what fraction of rows the predicate will match using stored statistics, converts that fraction into an estimated cost for each candidate path, and picks the cheapest. Low estimated selectivity favours the index; high favours the full scan.

open as a page

Explain what selectivity and cardinality mean to a query optimizer, how a predicate's selectivity is turned into an estimated row count, and why an error in an early estimate is more damaging than an error in the cost formula.

level: middleimportance: must knowfreq 58%

basics

~20 s

Selectivity is the fraction of rows a predicate keeps (0 to 1); cardinality is the resulting row count — input rows times selectivity. Every cost term scales with cardinality, and estimates feed into the next operator, so an early error multiplies up the plan tree and produces structurally wrong join and access choices.

open as a page

A single query joins eight tables. Why does the order in which the database engine joins them matter so much, and roughly how many possible orderings exist?

level: middleimportance: must knowfreq 55%

basics

~20 s

Join order never changes the result, but it changes the size of the intermediate results carried between operators, so cost can differ by orders of magnitude. The number of orderings grows factorially: eight tables give 40,320 left-deep orders and roughly 17 million bushy ones.

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

A reporting query ran in 40 milliseconds for months using an index, and now takes 30 seconds because the engine switched to a full table scan. Neither the query text nor the schema changed. How do you diagnose why the chosen access path flipped?

level: seniorimportance: must knowfreq 56%

basics

~20 s

Capture the current plan with estimated and actual row counts. If the estimate is far above reality, the input changed: stale or refreshed statistics, data growth, or skew across parameter values. Fix the estimate before overriding the plan.

open as a page

Optimizers typically estimate the combined selectivity of two AND-ed predicates by multiplying their individual selectivities. Explain the assumption behind that, describe a realistic case where it fails badly, and how you would address it.

level: seniorimportance: must knowfreq 50%

basics

~20 s

It assumes the columns are statistically independent. When they are correlated — a city implies its state, a model implies its manufacturer — multiplying selectivities counts the same restriction twice and severely under-estimates rows. Fix with multi-column statistics, a materialized combined column, or a schema change that removes the redundancy.

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

Between a plain index lookup with per-row table fetches and a full table scan, some engines offer a third option that first builds an in-memory bitmap of matching row locations and only then reads the table. What does that buy, and when would an optimizer choose it?

level: middleimportance: should knowfreq 40%

basics

~20 s

Collecting matches into a bitmap first lets the engine sort and deduplicate page addresses, then read table pages once each in physical order. That converts scattered random reads into a mostly sequential pass, and lets several indexes be combined before touching the table.

open as a page

A query optimizer assigns each candidate plan a numeric cost. What goes into that number, why do engines separate sequential from random page access in the formula, and what does the number actually mean?

level: middleimportance: should knowfreq 42%

basics

~20 s

Cost combines estimated I/O (pages touched, weighted differently for sequential versus random access) with CPU terms (per row processed, per predicate evaluated, per index entry). The result is an abstract unit for comparing plans on the same query, not a time prediction. Random access is weighted higher because scattered reads lose readahead and locality.

open as a page

What is the difference between a left-deep and a bushy join tree, and why do many query optimizers restrict their search to left-deep plans?

level: middleimportance: should knowfreq 38%

basics

~20 s

In a left-deep tree every join's right input is a base table, so joins form a pipeline. In a bushy tree both inputs can be intermediate results. Left-deep shrinks the search space from about (2n-2)!/(n-1)! to n! and pipelines well, but bushy plans can be far better for star-shaped queries and parallel execution.

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

Two tables of the same size each have a B-tree index on a filtered column, and a query matches the same number of rows in each, yet the optimizer costs the index path far higher on one of them. Which table property explains the difference, and how does the optimizer measure it?

level: seniorimportance: should knowfreq 38%

basics

~20 s

How closely the table's physical row order matches the index key order. When rows with adjacent keys sit on the same pages, one fetch serves many matches; when they are scattered, each match costs its own random page read. Optimizers store this as a clustering or correlation statistic.

open as a page

A query that normally runs in under a second occasionally takes minutes, with no schema or code change. How would you determine whether the optimizer's estimates or its cost model are to blame, and what are the common sources of such estimate failures?

level: seniorimportance: should knowfreq 45%

basics

~20 s

Capture the executed plan with actual row counts and compare them to the estimates operator by operator, bottom-up. The lowest node where estimated and actual diverge sharply is the cause. Common sources: stale statistics, correlated predicates, parameter-sensitive plans, opaque predicates, and values beyond the histogram's range.

open as a page

Many relational optimizers stop doing exhaustive join-order search once a query exceeds a certain number of joined tables and switch to a greedy or randomized/genetic algorithm. Why, and what do you give up?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Exhaustive dynamic programming is exponential — roughly 3^n time and 2^n memory — so past about a dozen tables the planning time and memory exceed any benefit. Engines switch to greedy construction or randomized/genetic search. You give up the guarantee of the cheapest plan under the cost model, and randomized search also gives up plan determinism.

open as a page

Explain how the classic System R (Selinger) dynamic-programming algorithm chooses a join order, and why it is cheaper than trying every ordering.

level: seniorimportance: should knowfreq 40%

basics

~20 s

It builds plans bottom-up over subsets of tables: best plan for every single table, then every pair, then every triple, keeping only the cheapest plan per subset. Reusing sub-results turns a factorial search into roughly 3^n time and 2^n memory. It also keeps extra plans that produce a useful sort order.

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

For a business-critical transactional service, how would you decide between letting the optimizer freely re-choose access paths as data changes and pinning plans so they stay fixed?

level: principalimportance: nice to knowfreq 33%

basics

~20 s

Default to letting the optimizer adapt, and invest in the inputs: good statistics and estimable predicates. Pin only the few statements whose worst case is unacceptable, treat each pin as a dated exception with an owner, and monitor pinned plans for decay.

open as a page

Optimizer-chosen plans can change on their own as data, statistics, or engine versions shift. For a system where some queries must never regress, how do you decide between letting the optimizer keep adapting and pinning plans, and what does that policy cost you?

level: principalimportance: nice to knowfreq 30%

basics

~20 s

Adaptivity and predictability trade off directly. Let the optimizer adapt by default, since it tracks data change; pin plans only for a small set of critical queries where a regression is unacceptable. Pinning freezes today's plan — including out of better future plans — so each pin needs an owner, a review date, and monitoring.

open as a page

A nightly reporting statement joins around 25 relations. It spends longer being planned than executing, and its chosen plan differs between runs. How would you approach this?

level: principalimportance: nice to knowfreq 24%

basics

~20 s

Both symptoms say the query is past the optimizer's exhaustive-search threshold and is being planned by a randomized heuristic. Reduce the effective relation count by decomposing the statement or materializing stable sub-results, make planning happen once via plan reuse or pinning, and fix the statistics that guide whichever search runs.

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