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?
answer
- Auto-threshold scales with size ⇒ big tables protected least
- Small hot: defaults, watch plan churn
- Huge append-only: lower scale factor + boundary staleness
- Loaded tables: refresh as final load step
- Targets per column; monitor staleness as an SLO
basics
~20 sTreat 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.
solid answer
~60 sI would stop thinking of it as one knob and segment by table class. **Small hot tables.** The default automatic threshold fires often and refreshing is cheap. Leave it alone; the risk here is over-refreshing and churning plans, not staleness. **Huge append-only tables.** A percentage-based trigger scales with table size, so at a billion rows it needs tens of millions of changes before firing — far too late. Lower the scale factor for these tables specifically. Watch the increasing-column trap: predicates beyond the histogram's last bucket estimate near zero, so recency matters more than volume here. **Nightly-loaded reporting tables.** Do not rely on background refresh at all. Make the statistics refresh the closing step of the load job, before the dependent workload starts, and include table-level statistics after partition-wise loads. **Sampling target** is a per-column decision: raise it for known-skewed, heavily-filtered columns and high-cardinality join keys; leave the rest at default, because gathering cost is paid on every refresh. And I would monitor **rows changed since last refresh** and **time since last refresh** on the top tables, so staleness is an alert rather than an incident post-mortem.
go deeper
Know that statistics need refreshing and that a bulk load should be followed by an explicit refresh.
Distinguish automatic from explicit refresh, and explain why percentage-based thresholds react late on large tables.
Segment by table class, sequence refresh into load jobs, handle partitioned and increasing-column cases, and raise targets selectively.
Express it as a freshness SLO per workload class with explicit tradeoffs — gathering cost, plan stability, automatic versus explicit coverage — plus monitoring that turns staleness into an alert.
## Why a single global policy is wrong Every mainstream engine ships an automatic statistics refresh with a threshold of the form *"refresh when changed rows exceed a constant plus a fraction of the table size"*. That formula has one deeply awkward property: **the bigger the table, the more change it tolerates before reacting**. At a 10% scale factor, a 10,000-row table refreshes after 1,000 changes; a billion-row table waits for 100 million. The tables where a wrong estimate is most expensive are the ones the default protects least. Meanwhile, refreshing costs real resources — a sampling pass and catalog writes — and refreshing too eagerly on a hot table churns plans for no benefit. So the policy is a genuine tradeoff, and it should be made per class of table. ## Class 1 — small, hot, frequently updated tables Characteristics: thousands to low millions of rows, high write rate, queried constantly. - Automatic refresh already fires often, and a sampling pass is cheap. - Leave defaults alone. The real risk here is *plan churn*: statistics moving slightly and flipping a marginal plan back and forth. - If plan instability is observed, the answer is usually to look at whether two plans are genuinely near-equal in cost, not to refresh more. ## Class 2 — very large append-only tables Characteristics: hundreds of millions to billions of rows, continuous inserts, rarely updated. Two distinct problems: 1. **Threshold scaling.** Lower the scale factor for these tables so refresh triggers on a sane absolute number of rows rather than a percentage. 2. **Boundary staleness matters more than volume.** On a monotonically increasing column — timestamp, sequence id — the harmful staleness is not the row count but the histogram's upper bound. Recent-data predicates fall outside the recorded range and estimate near-zero rows, producing plans sized for one row against millions. A relatively cheap, more frequent refresh limited to the increasing columns solves most of this. If the engine supports it, incremental or partition-level statistics avoid re-sampling the whole table on every refresh — but remember that table-level aggregates may still need updating separately. ## Class 3 — nightly-loaded reporting tables Characteristics: bulk-loaded in a batch window, then queried heavily by long-running statements. - **Refresh explicitly, as the last step of the load job.** This is non-negotiable; background refresh cannot be trusted to have run before the dependent workload starts. - Sequence it before the reports, and let the report job depend on the refresh completing. - For partition-wise loads, refresh the touched partitions *and* the table-level statistics. - Because these queries are long-running, a fuller sample is affordable here: the gathering cost is amortized over minutes of execution, so raised targets pay off more than they do in OLTP. ## The sampling-target dimension Separate from *when* to refresh is *how thoroughly*. The statistics target controls sample size and histogram/MCV resolution. - Raise it selectively: heavily-filtered skewed columns, high-cardinality join keys where distinct-value error propagates into join cardinality. - Do not raise it globally. The cost is paid on every refresh of every table, and most columns gain nothing. - Add multi-column/extended statistics only where correlated predicates are demonstrably causing bad estimates — they are not free either. ## Monitoring — the part most teams skip Make staleness observable: - rows changed since last refresh, and time since last refresh, for the top N tables by size or query volume - alert when a table crosses a staleness budget you have chosen deliberately per class - for the queries that matter, periodically compare estimated versus actual rows and alert on large divergence This converts "the report was slow last night" into "table X has not been analyzed in 40 hours", which is actionable before anyone notices. ## The tradeoffs to name explicitly - **Freshness versus gathering cost.** Refresh passes consume I/O and CPU on a live system; the more you refresh, the more you pay continuously to avoid an occasional bad plan. - **Freshness versus plan stability.** Every refresh can change a plan. On a marginal cost decision, more frequent refreshing means more frequent flips. Some workloads value predictability over optimality — batch jobs in particular. - **Automatic versus explicit.** Automatic covers the long tail of tables nobody thinks about; explicit covers the handful that matter and must be right at a specific moment. You want both, not a choice between them. ## The framing that lands *Statistics freshness is an SLO, not a setting. Define it per table class, meet it with automatic refresh where change is proportional and explicit refresh where change is bursty, spend sampling budget only where estimation error demonstrably costs plans, and monitor staleness so it is an alert rather than an incident.*
- Why is a percentage-based auto-refresh threshold badly suited to very large tables?Because the absolute number of changed rows required grows with the table. A 10% threshold means a billion-row table tolerates 100 million changes before refreshing, so the tables where a wrong estimate is most expensive are the slowest to react. Lowering the scale factor for those specific tables restores a sane absolute trigger.
- Can refreshing statistics too often cause problems?Yes, in two ways. Each refresh consumes I/O and CPU on a live system, and each refresh can change a plan — so on decisions where two plans are near-equal in cost, frequent refreshes produce plan churn and unpredictable latency. Workloads that value predictability, especially batch jobs, sometimes prefer a stable slightly-suboptimal plan.
- How do you decide which columns deserve a raised statistics target?Target the columns where estimation error demonstrably changes plans: heavily-filtered skewed columns, and high-cardinality join keys where distinct-value error propagates into join cardinality. Confirm by comparing estimated to actual rows on the queries that matter. Raising it globally is wrong because the gathering cost is paid on every refresh of every table for no benefit on most columns.
saying these in an interview costs you the question
- Proposing one global refresh setting for every table in the database
- Assuming more frequent refresh is always better, ignoring gathering cost and plan churn
- Raising the sampling target database-wide to fix a single column's estimate
- Trusting background auto-refresh to complete before a dependent nightly report starts
- Treating statistics staleness as something you discover from slow queries rather than monitor directly