skip to content

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%

answer

  1. invalidate for correctness: DDL, index, constraint, privileges
  2. recompile for quality: statistics refreshed / auto-stats threshold
  3. involuntary: restart, memory eviction, cache flush
  4. drift without a stats refresh = valid but stale plan
  5. ascending key: new values beyond the histogram range

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.

solid answer

~60 s

**Correctness-driven invalidation** - the plan can no longer be executed as compiled: - DDL on any referenced table, view, column or type; - adding, dropping or rebuilding an index the plan uses or could use; - constraint changes the optimizer relied on; - permission changes, or session settings that are part of the cache key. **Quality-driven recompilation** - the plan is still runnable but suspect: - statistics refreshed on a referenced table, manually or by the auto-statistics threshold on modified-row counts; - an engine-specific rule such as re-planning when a temporary table's or table variable's row count changes materially. **Involuntary loss:** server restart, memory pressure evicting the entry, an explicit cache flush. **The dangerous case:** data drifts steadily but no statistics refresh fires, so nothing the engine tracks has changed. The plan stays valid and stays cached while the assumptions it was costed against quietly stop being true - a table that had 10,000 rows at compile time now has 10 million. Nothing invalidates it; it just gets slower.

go deeper

for a junior

List the obvious triggers - schema changes, new or dropped indexes, statistics updates, restart - and say the plan is otherwise reused as-is.

for a middle

Separate correctness invalidation from quality recompilation, and explain that data can drift without any trigger firing.

for a senior

Tie triggers to incident timelines, explain the ascending-key estimate failure, and note that recompiling re-sniffs parameters so maintenance can cause the regression it was meant to prevent.

for a principal

Treat statistics freshness and recompilation rate as tunable reliability controls with a two-sided cost, and design monitoring that detects plan changes rather than waiting for user-visible latency.

## Two different reasons to throw a plan away Engines distinguish invalidation for **correctness** from recompilation for **quality**, and confusing them is a common interview stumble. **Correctness invalidation** is mandatory. The cached plan references catalog objects by identity - this table, this column ordinal, this index, this function overload. If any of them changes, the plan may be unrunnable or plainly wrong, so it must go. Triggers include: - any DDL on a referenced table or view - adding, dropping or retyping a column, renaming, replacing a view definition; - creating, dropping or rebuilding an index, since a new index opens an access path the optimizer never considered and a dropped one removes a path it chose; - constraint changes, because optimizers use constraints for reasoning - a `NOT NULL` or a check constraint can eliminate branches or enable transformations; - changes to the executing principal's privileges, or to row-level security policies; - changes to session settings that participate in the cache key or in plan choice. Engines implement this with dependency tracking: each cache entry records the objects and catalog versions it depends on, and a catalog change bumps a version that invalidates every dependent entry. **Quality recompilation** is discretionary. The plan would still run, but the engine believes it is no longer the best choice - almost always because the statistics it was costed against have been replaced. Concretely: - an explicit statistics refresh on a referenced table; - automatic statistics maintenance firing after a modified-row threshold is crossed; - engine-specific rules such as re-planning when a temporary object's cardinality changes materially, or an explicit per-statement recompile directive from the application. **Involuntary loss** is neither: a restart empties the cache, memory pressure evicts entries under an eviction policy, and administrators can flush the cache outright. The plan was fine; it is simply gone, and the next execution recompiles - which, importantly, re-runs parameter sniffing with whatever values arrive first. ## Why a valid plan can still be a bad plan This is the heart of the question. Invalidation is driven by **catalog and statistics events**, not by reality. Between those events, the data can drift arbitrarily: - a table grows from 10,000 to 10 million rows without crossing the auto-statistics threshold in a way that produces a timely refresh; - the distribution shifts - a status column that used to be 5% `PENDING` becomes 60% `PENDING` after an upstream slowdown; - values move outside the histogram's recorded range, the classic "ascending key" problem: rows inserted with today's timestamp fall beyond the highest value statistics know about, so a predicate on recent data is estimated at nearly zero rows and the optimizer picks a plan sized for nothing; - correlation between columns changes, so independence assumptions that were roughly true become badly wrong. In every case the cached plan remains perfectly *valid*: it references existing objects and existing indexes, so nothing invalidates it. It is simply costed against a world that no longer exists. The engine has no cheap way to notice, because noticing would mean measuring reality on every execution. ## What that implies operationally - **Statistics maintenance is a reliability control, not housekeeping.** The freshness of statistics determines both the quality of new plans and whether stale ones get replaced at all. - **Recompilation is not free and not neutral.** Every recompile re-runs planning, costing CPU and taking latches, and for a parameterized statement it re-sniffs values - so a routine maintenance job can flip a well-behaved statement into a bad plan by pure timing. Statistics jobs and restarts are therefore a common trigger for "it broke overnight, nothing changed". - **Ascending keys need explicit handling.** Predicates on recent timestamps or increasing identifiers are the most common victims of the drift problem; more frequent statistics on those columns, or physical design that avoids the estimate mattering, is the usual answer. - **Correlate incidents with events.** When a plan regression appears, the first questions are: was there DDL, an index change, a statistics refresh, a restart, or a cache flush at that moment? If yes, you have a recompile-plus-sniffing story. If no, you likely have drift against a plan that was never invalidated. - **Watch both directions.** Too little recompilation leaves stale plans in place; too much burns CPU and destabilizes plan choice. Neither extreme is safe, which is why engines expose thresholds rather than a switch. ## The sentence that answers the question A cached plan is invalidated when the *catalog* or *statistics* change, but it becomes wrong when the *data* changes - and those are not the same event. Plan quality decays silently in the gap between them.

  • Why can creating a new index cause a query that was already fast to change its plan and get slower?
    Creating an index invalidates cached plans on the table because a new access path exists, so the statement recompiles. The optimizer may now believe the new index is cheaper and choose it, and if its estimates are off - for example because the index is selective for typical values but not for the one being executed - the new plan can be worse. The regression comes from forced recompilation plus an estimate error, not from the index being intrinsically bad.
  • A plan regressed overnight with no deployment. What events would you check first?
    Anything that could have evicted or recompiled the plan: a scheduled statistics refresh, an index rebuild or other maintenance DDL, a server restart or failover, memory pressure evicting cache entries, and a cache flush. Any of these forces a recompile, which re-runs parameter sniffing against whatever value arrives first, so the new plan can be tuned for an unrepresentative execution. If none of them happened, suspect data drift against a plan that was never invalidated.

saying these in an interview costs you the question

  • Assuming a cached plan is re-validated against real data on each execution.
  • Not distinguishing invalidation for correctness from recompilation for plan quality.
  • Believing statistics refreshes are harmless housekeeping with no effect on cached plans.
  • Forgetting that a forced recompile re-sniffs parameter values and can therefore make things worse.
  • Thinking table growth alone always triggers a recompile.

context