skip to content

questions

11

What does a database execution plan tell you, and what is the difference between a plan the optimizer only predicts and a plan collected while the query actually ran?

level: juniorimportance: must knowfreq 72%

answer

  1. operator tree, not SQL text
  2. estimate-only vs run-and-measure
  3. cost = unitless internal units, never ms
  4. actual plan really executes (writes too)
  5. rows returned vs rows touched

basics

~20 s

A plan is the tree of physical operators the engine will run - scans, joins, sorts - with estimated rows and cost per node. A predicted plan shows estimates only; an execution-time plan really runs the query and adds actual rows, loop counts and timings.

solid answer

~50 s

A plan is the optimizer's chosen physical strategy: how each table is accessed (full scan vs index access), the order tables are joined in, which join algorithm is used, and where sorts, aggregations and materializations sit. Each node carries estimates - rows it expects to emit, row width, and a cost number in the optimizer's own internal units. There are two flavours. A **predicted, compile-only plan** is produced without executing anything: cheap and safe, but every number is a guess derived from statistics. An **execution-time plan** actually runs the query and annotates each node with what really happened - actual rows, number of executions, elapsed time, memory used, whether an operator spilled to temporary storage. Diagnosis needs the execution-time plan, because the interesting defect is nearly always the gap between what the optimizer expected and what the data really did. Fall back to the predicted plan when running the query would be too slow or too destructive.

go deeper

for a junior

Be able to say a plan is the engine's step-by-step strategy with per-operator row estimates and cost, and that an execution-time plan additionally reports what really happened.

for a middle

Name the common operator families, explain that cost is unitless, and say why you prefer an execution-time plan for diagnosis and what it costs you to get one.

for a senior

Frame the plan as evidence: rows first, cost second, and the estimate-versus-actual gap as the primary diagnostic signal. Mention safe capture on production (replica, rollback, plan capture from monitoring).

for a principal

Talk about plans as artefacts of a moment - statistics, volume and configuration - and about the practices that make them trustworthy: production-like data for testing, plan capture in monitoring, and validating fixes by measurement rather than by cost.

## A plan is a procedure, not your SQL SQL is declarative: you describe the result you want, not the steps. The optimizer converts that description into a procedure - a tree of physical operators - and the execution plan is that tree made visible. The plan is not a rewrite of your query text; two very different-looking queries can compile to the same plan, and the same query can compile to different plans on different days as data and statistics change. Typical operators you will see, whatever the engine calls them: - **Access methods**: full table scan, index range scan, index unique lookup, index-only access (all needed columns live in the index), bitmap/rowid access. - **Join methods**: nested loop (for each outer row, probe the inner side), hash join (build a hash table on the smaller input, probe with the larger), merge join (both inputs sorted, walk them together). - **Blocking/materializing operators**: sort, hash build, aggregate, spool/temp table. These must consume their whole input before emitting a row. - **Row-shaping operators**: filter, projection, limit, distinct, window computation. ## The numbers attached to each node Every node in a predicted plan carries the optimizer's guesses: - **estimated rows** - how many rows the optimizer believes this node emits, derived from table statistics (row counts, distinct values, histograms) and the predicates applied. - **row width** - average bytes per row, which drives memory and sort estimates. - **cost** - a *unitless internal number*, roughly a model of page reads plus CPU work, calibrated to the optimizer's own constants. It is not milliseconds and is only meaningful for comparing candidate plans for the same query on the same server configuration. An execution-time plan adds measured facts: **actual rows**, **executions/loops** (how many times the node was started), **actual elapsed time**, **memory granted/used**, and warnings such as spilling to disk. ## Why the two flavours matter The predicted plan answers 'what does the optimizer intend to do?'. That is enough to spot a structurally wrong shape - a full scan of a huge table where you expected an index lookup, a join order that starts from the biggest table, a sort you did not ask for. The execution-time plan answers 'what actually happened?'. Only it can show you that a node the optimizer expected to emit 50 rows emitted 2 million, that a nested loop ran 40,000 times, or that a sort spilled. Almost every hard performance bug is that mismatch, so the execution-time plan is the real diagnostic tool. The price is that the engine must genuinely run the query. That means: the full runtime is paid (a 40-minute query takes 40 minutes to explain this way), the load hits production, and for INSERT/UPDATE/DELETE the data really changes. The standard safety measures are to run it inside a transaction that you roll back, to run against a replica or a restored copy with comparable data volume and statistics, or to capture the plan from whatever plan-capture/monitoring facility the engine offers for queries already running. ## Reading discipline Three habits separate people who read plans from people who stare at them: 1. Look at **rows before cost**. Cost is a model; row counts are the thing the model gets wrong. 2. Look for the **cheapest ratio question**: how many rows did the query return versus how many rows did the plan touch? A query returning 10 rows after touching 10 million is an access-path problem regardless of what the cost says. 3. Never compare cost numbers **across** queries or servers. A cost of 12,000 for one query says nothing about a cost of 400 for another; it is only a currency inside one optimization decision. Finally, a plan is a snapshot of one compilation. It reflects the statistics, data volume and configuration at that moment. A plan captured on a small development database is evidence about shape, not about production behaviour.

  • Why can requesting an execution-time plan be risky on a production system?
    Because the engine really executes the statement: you pay the full runtime and full resource cost, and a data-modifying statement genuinely modifies data. Mitigations are to run it inside a transaction you roll back, to use a replica or a restored copy with production-like volume, or to capture the plan of the already-running statement from the engine's monitoring facilities.
  • Two plans for the same query show costs of 1,200 and 3,400. Does the cheaper one always run faster?
    No. Cost is the optimizer's model output based on estimated cardinalities, and if those estimates are wrong the cost ranking is wrong too. Cost also assumes a particular cache state and hardware calibration. The only authoritative comparison is measured execution - actual rows, time and I/O - which is why you validate a fix by running it, not by admiring a lower cost.

A predicted plan is the route your navigation app draws before you leave; an execution-time plan is the trip recorder afterwards, showing where you actually sat in traffic.

saying these in an interview costs you the question

  • Reading the plan as the order of SQL clauses (FROM, WHERE, SELECT) rather than an operator tree
  • Calling the cost number milliseconds or seconds
  • Comparing cost values between different queries or different servers
  • Claiming a predicted plan proves the query is fast, without ever running it
  • Not realising an execution-time plan on an UPDATE or DELETE actually changes data

context

open as a page

Databases keep a cache of already-compiled execution plans. Explain what such a plan cache stores, what entries are keyed on, and why two statements that mean exactly the same thing but differ in whitespace or literal values usually get separate cache entries.

level: middleimportance: must knowfreq 52%

basics

~20 s

The cache stores compiled physical plans so repeated statements skip parsing, binding and optimization. Entries are keyed on the statement text (plus session context like schema search order and some settings), and the key is essentially exact-match, so different whitespace or inlined literals produce different keys and separate entries.

open as a page

How do you read an execution plan tree - which operator runs first and how does data flow between nodes - and how do you interpret a node reported as '8 rows, 40,000 executions'?

level: middleimportance: must knowfreq 68%

basics

~20 s

Read innermost/deepest nodes first: leaves produce rows, parents consume them, results flow upward to the root. A node showing 8 rows over 40,000 executions was started 40,000 times and produced about 8 rows each time - roughly 320,000 rows in total, so multiply before judging cost.

open as a page

A parameterized query normally returns in milliseconds but intermittently takes minutes for days at a time, with no schema change and no data-volume change, and it recovers as soon as the statement is recompiled. Explain the plan-reuse behaviour that causes this and how you would confirm it.

level: seniorimportance: must knowfreq 50%

basics

~20 s

The engine compiled the plan using the first parameter values it saw and cached it. If those values were unrepresentative - very selective or very unselective compared with typical ones - every later execution reuses a plan tuned for the wrong case. Recompiling with different values silently swaps which case suffers.

open as a page

In an execution-time plan, one operator estimated 50 rows but actually produced 2 million. What does that gap tell you, what typically causes it, and how does it make the query slow?

level: seniorimportance: must knowfreq 62%

basics

~20 s

It is a cardinality misestimate: the optimizer planned for a tiny input and got a huge one, so it likely chose the wrong join method, join order and memory grant. Causes are stale or missing statistics, correlated predicates, and predicates the optimizer cannot see through. Find the lowest node where the gap starts.

open as a page

An application builds SQL by concatenating literal values directly into the statement text instead of sending bind parameters. Beyond the SQL-injection risk, describe what this does to the database's execution plan cache and to server CPU under load.

level: middleimportance: should knowfreq 44%

basics

~20 s

Every distinct literal produces a distinct cache key, so the plan cache fills with thousands of near-identical single-use entries. Useful plans get evicted, the hit rate collapses, and the server burns CPU re-optimizing the same query shape over and over, adding latency to every call.

open as a page

When a prepared statement is executed repeatedly with different parameter values, a database can either keep one parameter-independent plan or optimize afresh for each execution's values. Compare the two strategies, and describe how an engine can decide between them automatically.

level: seniorimportance: should knowfreq 36%

basics

~20 s

A generic plan is compiled once using average selectivity and reused for all values: cheap, stable, but never value-specific. A custom plan is optimized per execution using the actual values: accurate, but pays optimization cost every time. Engines can compare observed custom-plan costs against the generic plan's cost and switch when generic is not worse.

open as a page

A database can discard or rebuild a cached execution plan rather than reuse it. What events cause that, and why can a plan that was never invalidated still be a bad plan today?

level: seniorimportance: should knowfreq 34%

basics

~20 s

Plans are invalidated by DDL on referenced objects, index or constraint changes, statistics refreshes, permission or session-setting changes, explicit cache flushes, restarts, and memory-pressure eviction. But data drifting without a statistics refresh changes nothing the engine tracks, so a valid cached plan can quietly become wrong.

open as a page

What does it mean when an execution plan reports that a sort or hash operation spilled to disk or used temporary storage, and how do you respond?

level: seniorimportance: should knowfreq 46%

basics

~20 s

The operator needed more working memory than it was granted, so it wrote partitions or runs to temporary storage and made extra passes. It is usually caused by a low row estimate, an over-wide row, or a memory budget too small for the real data. Fix the estimate first, then the sort or projection.

open as a page

You own an OLTP service whose hottest query filters on a tenant column where one tenant holds roughly 80% of the rows. The statement is parameterized, plans are shared across tenants, and latency is bimodal. How would you decide between forcing re-planning per execution, splitting the statement, or reshaping the data - and what would you measure to justify the choice?

level: principalimportance: should knowfreq 28%

basics

~20 s

Quantify first: call rate per class, cost of the right versus wrong plan, and optimization cost per compile. High-rate short queries cannot afford per-execution planning, so split the statement so each class gets its own stable plan. If the heavy tenant dominates capacity, reshape - partition or isolate it - so no plan choice is required.

open as a page

Given a slow query and its execution-time plan, how do you decide whether the right fix is a new index, a query rewrite, better statistics, or accepting the query as it is?

level: principalimportance: should knowfreq 42%

basics

~20 s

Classify the problem first. Rows touched far exceeding rows returned means an access-path problem (index). A large estimate-versus-actual gap means an estimation problem (statistics or rewrite). Redundant work in the plan means a semantics problem (rewrite). Genuinely large output means the query is simply big - change the workload, not the plan.

open as a page