Query Processing & Optimization
How a relational engine turns my SQL text into results: parsing, rewriting, cost-based planning, and physical execution. Interviewers probe this axis to see whether I can reason about why a query is slow from the engine's point of view, not just tweak the SQL.
part ofRelational database conceptsoverview, primer and where to startread it →on this pageshowhide
explore
- Pipeline: Parse & Rewrite12 questions
- Query Lifecycle Stages4 questions
- Rewrites & Predicate Pushdown4 questions
- Subquery Decorrelation4 questions
- Cost-Based Optimization22 questions
- Cost Model & Cardinality Estimation6 questions
- Statistics & Histograms6 questions
- Access-Path Selection5 questions
- Join Ordering & Search-Space Pruning5 questions
- Execution Engine27 questions
- Nested Loop Join4 questions
- Hash Join5 questions
- Merge Join4 questions
- Sorting & Aggregation Operators6 questions
- Iterator vs Vectorized Execution4 questions
- Materialization vs Pipelining4 questions
- Plans in Practice11 questions
- Reading Execution Plans5 questions
- Plan Caching & Parameter Sniffing6 questions
- BI Analystroleanchors this topic
- PostgreSQL DBAroleanchors this topic
- SQLskillanchors this topic
- AI & Data Scientistrole
- AI Engineerrole
- Backend Developerrole
- Computer Scienceskill
- Cyber Security Expertrole
- Data Analystrole
- Data Engineerrole
- Forward Deployed Engineerrole
- Full Stack Developerrole
- Java Backend Developerrole
- Kotlin Backend Developerrole
- MLOps Engineerrole
- Machine Learning Engineerrole
- Server-Side Game Developerrole
- Software Architectrole
questions
72 · 4 sectionsIn a query plan, what does it mean to "push a predicate down", and what does the optimizer gain by doing it? Include projection pushdown in your answer.
basics
~20 sPushdown moves a filter as close to the data source as possible - below joins and into scans - so fewer rows travel up the plan. Projection pushdown does the same for columns: read and carry only the columns actually needed. Both cut I/O, memory and CPU.
When an optimizer flattens a WHERE EXISTS or WHERE IN subquery, what join shape does it produce, and how does that shape differ from an ordinary inner join?
basics
~20 sIt produces a semi-join: for each outer row it checks whether at least one matching inner row exists, then emits the outer row once and stops probing. An inner join emits one output row per matching pair, so it can duplicate outer rows and it exposes inner columns.
Under what condition may an optimizer legally convert a LEFT OUTER JOIN into an INNER JOIN, and why is that conversion worth making?
basics
~20 sWhen a predicate applied after the join rejects NULLs on the null-supplying side, the NULL-extended rows cannot survive anyway, so the outer join is equivalent to an inner join. Converting is worth it because inner joins can be reordered freely and get more access-path and pushdown options.
In a relational database, what is the difference between a logical plan and a physical plan for the same query, which component produces each, and why does the distinction exist at all?
basics
~20 sA logical plan says what result is wanted - relational operators like scan, filter, join, aggregate - with no algorithms. A physical plan says how: chosen access methods, join algorithms and order, with costs. Binding and rewriting produce the logical plan; the cost-based optimizer produces the physical one.
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.
basics
~20 sA 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.
What are optimizer statistics in a relational database, and what goes wrong when they are missing or out of date?
basics
~20 sThey 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.
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.
basics
~20 sThe 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.
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.
basics
~20 sSelectivity 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.
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?
basics
~20 sJoin 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.
The same amount of data is scanned by two queries, yet one starts returning rows almost immediately while the other returns nothing for several seconds and then delivers everything at once. What in the execution plan explains the difference?
basics
~20 sA fully pipelined plan passes each row up to the client as it is produced, so the first row arrives early. If the plan contains a blocking operator — a sort, a hash build, a grouping step — nothing can be emitted until that operator has read all its input, so results appear only at the end.
Describe how a naive nested loop join produces its result, and what its cost is in terms of the sizes of the two inputs.
basics
~20 sFor each row of the outer input, scan the entire inner input and emit the pairs that satisfy the join predicate. Comparisons are O(n*m), and the naive form re-reads the inner input once per outer row, so I/O is the real problem.
When a database sorts or groups a large result set, the plan may report that the operator "spilled to disk". What does spilling mean, what triggers it, and how does it show up in query performance?
basics
~20 sEach sort or grouping operator gets a limited memory budget. If the rows it must hold exceed that budget, the engine writes partial results into temporary files and reads them back later. Spilling replaces memory work with disk I/O, so latency jumps sharply.
Explain how a database execution engine performs a hash join between two tables, describing what happens in each phase and when the optimizer is likely to choose this join method.
basics
~20 sPhase one (build): read the smaller input fully and hash its join-key values into an in-memory hash table. Phase two (probe): stream the larger input, hash each row's key, look it up, and emit matches. It is chosen for equality joins over large inputs with no useful index.
What is vectorized query execution, and why does processing a batch of rows per operator call typically run several times faster than processing one row per call?
basics
~20 sEach operator call returns a batch — commonly around 1024 rows held column-wise — instead of one row. Per-call dispatch is amortized over the batch, and the inner loops become tight, branch-free, cache-resident and SIMD-friendly, so far more cycles go into real work.
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?
basics
~20 sA 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.
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.
basics
~20 sThe 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.
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'?
basics
~20 sRead 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.
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.
basics
~20 sThe 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.
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?
basics
~20 sIt 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.