query plans
Query Plans
Learn how database optimizers transform SQL into executable operations, how to read estimated and actual execution plans, interpret scans, seeks, joins, sorts, estimates, lookups, spills, and parallelism, and optimize queries using measured evidence.
Introduction
SQL describes the result an application needs, but it usually does not prescribe every physical step the database must use to produce that result.
The database query optimizer examines the query, schema, indexes, constraints, and statistics. It then selects an execution strategy containing operations such as scans, seeks, joins, filters, sorts, and aggregations.
This strategy is represented by a query execution plan, also called a query plan or execution plan.
A query plan can show:
- The order in which tables are accessed
- Whether tables or indexes are scanned or searched
- The join methods selected
- The join order
- Where filters are evaluated
- How rows are sorted and grouped
- Estimated row counts
- Actual row counts when runtime information is collected
- Estimated operator costs
- Memory requirements
- Warnings, spills, lookups, or parallel operations
Core idea: Do not optimize a SQL query by guessing. Inspect the execution plan, compare estimates with runtime evidence, identify the operation performing unnecessary work, make one justified change, and measure again.
In your System Design curriculum, Query Plans is Topic 5.8 under Relational Data Modeling & SQL. It follows B-tree and composite indexes and precedes connection pools.
Prerequisites
| # | Prerequisite | Why It Is Needed |
|---|---|---|
| 1 | SQL queries | Execution plans represent how SQL statements are processed. |
| 2 | Joins and aggregations | Plans contain physical join, grouping, and aggregation operators. |
| 3 | B-tree and composite indexes | The optimizer compares available table and index access paths. |
| 4 | Transactions and isolation | Locking, row versioning, and concurrency can influence runtime behaviour. |
| 5 | Statistics and cardinality | The optimizer estimates how many rows each operator will process. |
| 6 | Performance measurement | Plans should be interpreted with duration, CPU, reads, writes, and returned-row evidence. |
What Is a Query Plan?
A query plan is a tree of physical operators the database uses to execute a SQL statement.
SQL statement
|
v
Parse and validate
|
v
Transform into logical operations
|
v
Evaluate candidate physical strategies
|
v
Choose an execution plan
|
v
Execute plan operators
|
v
Return result
The plan can specify table-access methods, join algorithms, filtering, aggregation, sorting, and data movement.
Query-processing Stages
Terminology differs between database systems, but query processing commonly involves several stages.
| Stage | Purpose |
|---|---|
| Parsing | Checks the SQL syntax and creates an internal representation |
| Binding | Resolves referenced tables, columns, data types, functions, and other objects |
| Transformation | Rewrites logical expressions into equivalent forms where useful |
| Optimization | Evaluates candidate access paths, join orders, and physical operators |
| Execution | Runs the selected physical operators and returns rows |
Query Optimizer
The query optimizer is the database component responsible for selecting an execution plan.
Optimizer inputs can include:
- The SQL query
- Tables and columns
- Available indexes
- Primary, foreign, and unique constraints
- Column and index statistics
- Data types and collations
- Query parameters
- Database configuration
- Available memory and processor resources
The optimizer considers alternative physical plans and selects one according to its cost model and search strategy.
Optimization rule: The optimizer searches for a sufficiently efficient plan within its optimization process. The selected plan is based on estimates and does not guarantee the fastest possible runtime for every parameter value and workload condition.
Logical vs Physical Operations
| Type | Meaning | Example |
|---|---|---|
| Logical operation | Describes the relational result required | Join orders with customers |
| Physical operation | Describes how the database obtains that result | Hash join, nested-loop join, or merge join |
SELECT
o.order_number,
c.display_name
FROM orders AS o
INNER JOIN customers AS c
ON c.customer_id = o.customer_id;
The SQL specifies a logical inner join. The optimizer selects a suitable physical join algorithm.
Cost-based Optimization
A cost-based optimizer estimates the resources required by candidate plans.
Cost considerations can include:
- Estimated rows
- Data-page access
- CPU work
- Memory use
- Sort work
- Join work
- Parallel execution
- Repeated row lookups
Cost values are optimizer-specific estimates. A displayed cost is not necessarily elapsed time in milliseconds or a directly portable measurement between plans, servers, or database systems.
Cardinality Estimation
Cardinality estimation predicts how many rows an operator will produce.
Table rows:
1,000,000
Predicate:
order_status = 'cancelled'
Estimated matching rows:
10,000
Estimated selectivity:
1%
The estimated cardinality influences access paths, join order, join algorithm, memory allocation, and parallelism.
A simplified selectivity expression is:
\[ Selectivity = \frac{MatchingRows}{TotalRows} \]
Estimated vs Actual Plans
| Plan Type | When Produced | Information |
|---|---|---|
| Estimated plan | Before executing the query | Optimizer estimates and selected operations |
| Actual plan | With or after query execution | The selected operators plus supported runtime counters |
An actual execution plan does not necessarily mean that every displayed value is actual. Cost values and some properties can remain estimates, while row counts, executions, timing, warnings, and memory information can include runtime evidence depending on the platform.
Analysis rule: Compare estimated and actual row counts. A large mismatch can explain an unsuitable join strategy, insufficient memory, unexpected lookups, or an inefficient table-access choice.
EXPLAIN
Many database systems provide an EXPLAIN command for obtaining a planned execution strategy.
EXPLAIN
SELECT
order_id,
order_status,
ordered_at
FROM orders
WHERE tenant_id = :tenant_id
AND customer_id = :customer_id
ORDER BY
ordered_at DESC;
Syntax and output differ between databases. Some systems support additional options for runtime statistics, buffer usage, formatting, or plan analysis.
Safety warning: A runtime plan command can execute the statement. Use caution with INSERT, UPDATE, DELETE, expensive queries, and production systems. Verify the exact command semantics before running it.
Reading an Execution-plan Tree
A plan is commonly represented as a tree. Parent operators consume rows produced by child operators.
SELECT result
|
v
Sort
|
v
Nested-loop join
|
+-- Customer index seek
|
+-- Order index seek
Graphical plans can be displayed in different reading directions. Follow the arrows and operator relationships shown by the selected database tool.
Important Operator Information
For each meaningful operator, inspect:
- Physical operation
- Logical operation
- Object being accessed
- Predicate
- Estimated rows
- Actual rows where available
- Number of executions
- Rows read or examined
- Output columns
- Estimated or actual memory usage
- Warnings
- Parallel execution properties
Table and Index Scans
A scan examines a broader portion of a table or index.
Table scan:
Read table pages
Evaluate predicate
Return matching rows
Index scan:
Read index entries
Evaluate predicate
Return or locate matching rows
A scan can be appropriate when:
- The table is small
- The query needs a large percentage of its rows
- No suitable index exists
- The index contains all required columns
- Ordered index access avoids other work
Scan rule: A scan is not automatically a problem. The important question is whether the amount of data examined is reasonable for the number of rows required.
Index Seek
An index seek navigates to a qualifying key or key range.
SELECT
customer_id,
display_name
FROM customers
WHERE customer_number = :customer_number;
CREATE UNIQUE INDEX ix_customers_number
ON customers
(
customer_number
);
A seek can still process many rows when the requested range is broad. The word “seek” alone does not prove that the query is efficient.
Seek Predicates and Residual Predicates
An index operation can use one condition to navigate the index and another condition to filter rows afterward.
CREATE INDEX ix_orders_tenant_date
ON orders
(
tenant_id,
ordered_at
);
SELECT
order_id
FROM orders
WHERE tenant_id = :tenant_id
AND ordered_at >= :start_time
AND order_status = 'pending';
The tenant and date conditions can define the index range. The status condition can remain as a residual filter when status is not part of a suitable index key.
Inspect:
- Rows retrieved from the index
- Rows removed by the residual predicate
- Rows finally returned
Key or Row Lookups
A secondary index can identify matching rows but lack columns required by the query. The database can then perform additional lookups against the underlying table or clustered structure.
Secondary index seek
|
v
Matching index entries
|
v
Lookup table row for additional columns
|
v
Return complete result
A few lookups can be efficient. Thousands or millions of repeated lookups can become expensive.
Lookup Example
SELECT
order_id,
order_status,
total_amount,
shipping_address
FROM orders
WHERE customer_id = :customer_id;
An index containing only customer_id and the row locator can
require additional retrieval for status, total, and address.
Possible responses include:
- Accept the lookups when few rows are returned
- Reduce the selected columns
- Add carefully chosen included columns where supported
- Use a different index
- Change the access pattern
Join Operators
A query plan can use different physical algorithms to implement a logical join.
- Nested-loop join
- Hash join
- Merge join
The best algorithm depends on input sizes, indexes, ordering, predicates, memory, and estimated cardinalities.
Nested-loop Join
A nested-loop join processes rows from one input and searches the other input for matches.
Outer input row 1
|
+--> Find matching inner rows
Outer input row 2
|
+--> Find matching inner rows
Outer input row 3
|
+--> Find matching inner rows
A nested-loop join is commonly suitable when:
- The outer input is small
- The inner input has an efficient indexed lookup
- The query returns a relatively small result
It can become expensive when the outer input is much larger than estimated and the inner lookup executes repeatedly.
Hash Join
A hash join builds a hash structure from one input and probes it with rows from another input.
Build input
|
v
Create hash structure
|
v
Probe with other input
|
v
Return matching rows
A hash join can be suitable when:
- Larger inputs are joined
- The join uses equality conditions
- Useful input ordering is unavailable
- An indexed nested-loop strategy would require many lookups
Hash joins can require significant memory. Insufficient memory can cause additional temporary-storage work.
Merge Join
A merge join consumes inputs ordered by compatible join keys.
Ordered input A
\
\
--> Merge matching keys --> Result
/
/
Ordered input B
A merge join can be suitable when:
- Both inputs are already ordered compatibly
- Large input sets are joined
- The join condition supports ordered comparison
If the inputs are not already ordered, the cost of sorting them can reduce the benefit.
Join-strategy Comparison
| Join | Common Strength | Potential Concern |
|---|---|---|
| Nested loop | Small outer input with efficient inner lookup | Many repeated inner operations |
| Hash join | Large equality joins without useful ordering | Memory requirement and temporary-storage spills |
| Merge join | Large, compatibly ordered inputs | Sorting cost when order is unavailable |
Sort Operator
A sort operator orders rows for an ORDER BY, merge operation, window function, grouping strategy, or another plan requirement.
SELECT
order_id,
ordered_at
FROM orders
WHERE customer_id = :customer_id
ORDER BY
ordered_at DESC,
order_id DESC;
A compatible composite index can sometimes provide the requested order:
CREATE INDEX ix_orders_customer_date_id
ON orders
(
customer_id,
ordered_at DESC,
order_id DESC
);
A sort is not automatically inefficient. Its impact depends on row count, row width, memory, and whether it spills to temporary storage.
Aggregation Operators
The database can implement GROUP BY and aggregate calculations using different physical strategies.
Hash Aggregate
Input rows
|
v
Hash by grouping key
|
v
Maintain aggregate state
|
v
Return groups
Stream Aggregate
Input ordered by grouping key
|
v
Accumulate consecutive group
|
v
Return one result per group
A stream-based aggregation can benefit from appropriately ordered input. A hash-based aggregation can process unordered data but can require more memory.
Filter Operator
A filter operator removes rows that do not satisfy a predicate.
Rows read:
100,000
Rows passing filter:
100
Observation:
The plan examined far more rows
than the query returned.
A large gap between rows read and rows returned can indicate a missing or poorly ordered index, a non-sargable predicate, an implicit conversion, or a deliberately broad access pattern.
Compute-scalar Operations
Compute operations evaluate expressions such as arithmetic, conversions, conditional expressions, or generated output values.
SELECT
quantity * unit_price
AS line_amount
FROM order_lines
WHERE order_id = :order_id;
A scalar calculation is not automatically expensive. Its impact depends on complexity and the number of rows on which it is evaluated.
Spool or Temporary-work Operators
Some optimizers temporarily store intermediate rows so they can be reused, protected, or processed again.
Child operator produces rows
|
v
Temporary stored result
|
+--> Reused by one branch
|
+--> Reused by another branch
A temporary spool can be beneficial when it avoids repeatedly performing expensive work. It can also indicate repeated consumption, missing access paths, or a need for protection required by the plan.
Parallelism
Some databases can divide query work among several workers.
Large input
|
v
Distribute work
|
+-- Worker 1
+-- Worker 2
+-- Worker 3
+-- Worker 4
|
v
Combine results
Parallelism can reduce elapsed time for suitable workloads, but it consumes additional worker and coordination resources. A parallel plan is not automatically better than a serial plan.
Memory Grants
Sorts, hash joins, and hash aggregations can request execution memory.
Estimated rows and row width
|
v
Optimizer estimates memory need
|
v
Memory grant requested
|
+--> Sufficient:
| perform operation in memory
|
+--> Insufficient:
write temporary data
to database work storage
Underestimated rows can produce a grant that is too small and cause a spill. Overestimated rows can reserve more memory than necessary and reduce concurrency.
Spills
A spill occurs when an operator such as a sort or hash operation cannot complete within its available execution memory and uses temporary storage.
Possible causes include:
- Underestimated cardinality
- Wide rows
- Large input sets
- Memory pressure
- Unsuitable join or aggregation choice
- Missing filtering opportunities
Do not respond to every spill by increasing memory. First investigate the query, estimates, predicates, indexes, statistics, and workload.
Estimated vs Actual Rows
Comparing estimated and actual rows is one of the most useful plan-analysis techniques.
| Estimate | Actual | Possible Effect |
|---|---|---|
| 10 | 500,000 | A repeated nested-loop operation, small memory grant, or unexpected lookup volume |
| 500,000 | 10 | Excess memory grant or overly broad cost assumptions |
| Close to actual | Close to estimate | The optimizer had more accurate cardinality information |
A mismatch is a diagnostic clue, not a complete diagnosis. Determine why the estimate was inaccurate.
Statistics
Database statistics summarize data distribution and help the optimizer estimate cardinality.
Statistics can describe:
- Approximate row counts
- Distinct-value counts
- Value distribution
- Null frequency
- Key density
- Relationships between selected indexed values
Stale or insufficient statistics can produce inaccurate estimates and unsuitable plans.
Statistics rule: An index can exist and still be ignored when the optimizer estimates that another plan is cheaper. Inspect the estimates and statistics before forcing an index.
Data Skew
Data skew occurs when values are distributed unevenly.
Order status distribution:
completed:
8,000,000 rows
pending:
25,000 rows
failed:
2,000 rows
One plan can be suitable for a rare status and unsuitable for a very common status.
Parameter-sensitive Plans
A cached plan selected using one parameter pattern can perform poorly for another parameter pattern.
SELECT
order_id,
ordered_at
FROM orders
WHERE tenant_id = :tenant_id
AND order_status = :status;
Tenant A:
100 orders
Tenant B:
50,000,000 orders
Same SQL shape:
Potentially different efficient access strategy
Parameter sensitivity should be confirmed using plan and runtime evidence. Available remedies are database-specific and should be selected carefully.
Plan Caching and Reuse
Database systems can cache execution plans and reuse them for subsequent executions.
Query submitted
|
v
Find reusable cached plan
|
+--> Found:
| execute cached plan
|
+--> Not found:
compile or optimize plan
cache where applicable
execute plan
Plan reuse reduces compilation work. However, schema changes, statistics updates, configuration changes, memory pressure, parameter issues, or database-specific invalidation rules can trigger a new plan.
Sargable Predicates
A sargable predicate allows the database to use an indexed value as a search argument.
SELECT
order_id
FROM orders
WHERE DATE(ordered_at) = :order_date;
SELECT
order_id
FROM orders
WHERE ordered_at >= :day_start
AND ordered_at < :next_day_start;
Compare the plans and examined rows rather than assuming that every function prevents index use on every database.
Implicit Conversions
When compared values have incompatible types, the database can introduce an implicit conversion.
Indexed column:
customer_number VARCHAR
Parameter:
Integer
Potential result:
Conversion appears in the plan
and index navigation becomes less efficient.
Match application parameter types with database column types and inspect plan warnings or scalar expressions.
SELECT * and Row Width
SELECT
*
FROM orders
WHERE customer_id = :customer_id;
SELECT
order_id,
order_number,
order_status,
ordered_at
FROM orders
WHERE customer_id = :customer_id;
Wider rows can increase data access, transfer, memory consumption, lookup cost, sorting cost, and client-processing work.
Pagination Plans
Deep offset pagination can require the database to process and discard many preceding rows.
SELECT
order_id,
ordered_at
FROM orders
ORDER BY
ordered_at DESC,
order_id DESC
LIMIT 20
OFFSET 100000;
Cursor or keyset pagination can navigate from a known ordered position:
SELECT
order_id,
ordered_at
FROM orders
WHERE
ordered_at < :cursor_ordered_at
OR
(
ordered_at = :cursor_ordered_at
AND order_id < :cursor_order_id
)
ORDER BY
ordered_at DESC,
order_id DESC
LIMIT 20;
Compare actual rows processed, reads, duration, and plan operations for both strategies using representative data.
Missing-index Suggestions
Some platforms can display index suggestions in execution plans.
Treat a suggestion as a candidate for analysis, not an automatic production instruction.
Before creating a suggested index, review:
- Whether a similar index already exists
- Composite column order
- Included-column width
- Write frequency
- Storage impact
- Other queries using the table
- Business uniqueness requirements
- Whether query rewriting solves the issue
Index Hints and Plan Forcing
Some databases allow index hints, join hints, or forced plans. These can override part of the optimizer's normal decision process.
Hint warning: Use hints only after understanding the root cause and operational consequences. A forced plan that works for current data can become inefficient after growth, distribution changes, schema changes, or statistics updates.
Query-plan Analysis Workflow
- Capture the exact slow query and parameter types.
- Confirm the database, schema, and environment.
- Record execution duration and returned rows.
- Capture the estimated plan.
- Capture runtime plan evidence safely where appropriate.
- Identify large scans, repeated lookups, joins, sorts, and spills.
- Compare estimated and actual rows.
- Inspect predicates and implicit conversions.
- Inspect statistics and data skew.
- Review available indexes and composite column order.
- Check selected columns, pagination, and query shape.
- Make one justified change.
- Capture the new plan and measurements.
- Measure write and concurrency impact.
- Retain evidence for regression testing.
Worked Example
Original Query
SELECT
o.order_id,
o.order_number,
o.order_status,
o.ordered_at,
c.display_name
FROM orders AS o
INNER JOIN customers AS c
ON c.customer_id = o.customer_id
WHERE
o.tenant_id = :tenant_id
AND o.order_status = :status
AND DATE(o.ordered_at) = :order_date
ORDER BY
o.ordered_at DESC;
Possible Plan Evidence
Observed plan characteristics:
- Large orders scan
- Function applied to ordered_at
- Many rows filtered after reading
- Explicit sort
- Customer lookup repeated for every matching order
Query Rewrite
SELECT
o.order_id,
o.order_number,
o.order_status,
o.ordered_at,
c.display_name
FROM orders AS o
INNER JOIN customers AS c
ON c.customer_id = o.customer_id
WHERE
o.tenant_id = :tenant_id
AND o.order_status = :status
AND o.ordered_at >= :day_start
AND o.ordered_at < :next_day_start
ORDER BY
o.ordered_at DESC;
Candidate Index
CREATE INDEX ix_orders_tenant_status_date_customer
ON orders
(
tenant_id,
order_status,
ordered_at DESC,
customer_id
);
Required Validation
- Compare the old and new plans
- Compare rows read with rows returned
- Check whether the sort remains
- Check join and lookup execution counts
- Measure runtime with representative parameters
- Measure index-maintenance impact on writes
Query Plans and Production Safety
Plan investigation can impose load on a production database. Runtime plan collection can execute expensive statements or add monitoring overhead.
Use:
- Approved diagnostic procedures
- Representative lower environments where possible
- Read-only reproduction when appropriate
- Bounded query timeouts
- Controlled parameter values
- Redaction of sensitive literals and plans
- Change-management and rollback procedures
Execution plans can contain object names, predicates, parameter values, or other sensitive metadata. Store and share them according to the applicable data-handling policy.
Query-performance Observability
Useful metrics include:
- Execution count
- Average and high-percentile duration
- CPU time
- Logical and physical reads
- Rows returned
- Rows examined
- Temporary-storage usage
- Memory-grant size
- Spill count
- Compilation count
- Plan changes
- Lock-wait time
- Timeout count
- Parameter-sensitive regressions
Common Query-plan Mistakes
Optimizing without Capturing the Plan
Guessing can add unnecessary indexes or rewrite a query without addressing the operation performing the most work.
Looking Only at the Highest Percentage Cost
Displayed costs are estimates relative to the selected plan. Runtime rows, reads, executions, waits, and warnings can provide more useful evidence.
Assuming Every Scan Is Bad
A scan can be appropriate for small tables or queries returning a large portion of the data.
Assuming Every Seek Is Good
A broad seek can read many rows, and repeated seeks can become expensive.
Ignoring Estimated and Actual Row Differences
Cardinality errors can lead to unsuitable joins, memory grants, lookups, and access paths.
Creating Every Suggested Index
Automatically accepting suggestions can create overlapping, wide, and expensive indexes.
Ignoring Residual Predicates
An index seek can still read many rows and discard most of them afterward.
Ignoring Lookup Execution Count
A low-cost lookup executed many times can dominate the total work.
Ignoring Parameter Sensitivity
One cached plan may not perform consistently across highly different parameter values.
Forcing a Plan Too Early
Plan forcing can hide inaccurate estimates, stale statistics, poor predicates, or missing indexes while creating future maintenance risk.
Testing with Unrealistic Data
Small or uniformly distributed test data can produce a plan unlike the plan selected for production volume and skew.
Ignoring Write and Concurrency Impact
A new index can improve one read while increasing write cost, lock time, logging, storage, and maintenance.
Recommended Test Cases
| Test | Expected Evidence |
|---|---|
| Estimated plan | The selected access paths and estimated rows are recorded |
| Runtime plan | Actual rows and supported runtime warnings are recorded |
| Estimate accuracy | Estimated and actual cardinalities are compared |
| Index seek | Rows read and returned from the seek are compared |
| Table or index scan | The reason and amount of scanned data are documented |
| Repeated lookup | The lookup execution count and total work are measured |
| Join strategy | The selected join is evaluated against actual input sizes |
| Sort operation | Rows sorted, memory, and spill evidence are captured |
| Non-sargable predicate | Plans before and after rewriting are compared |
| Parameter skew | Plans and runtimes for rare and common values are compared |
| Candidate index | Read improvement and write cost are both measured |
| Representative scale | Plan behaviour is validated with realistic volume and distribution |
Query-plan Best Practices
Recommended Practices
- Capture the exact query and parameter types.
- Begin with user-visible runtime and workload evidence.
- Inspect estimated and actual plan information where safe.
- Compare estimated rows with actual rows.
- Inspect rows read as well as rows returned.
- Evaluate scans according to data volume and required output.
- Evaluate seeks according to range width and execution count.
- Inspect residual predicates and implicit conversions.
- Review nested-loop inner-operation counts.
- Review hash and sort memory requirements and spills.
- Check whether ordering can be supported by an appropriate index.
- Keep predicates sargable where practical.
- Select only the columns required by the client.
- Maintain suitable statistics.
- Test skewed and representative parameter values.
- Treat index suggestions as candidates rather than commands.
- Make one tuning change at a time.
- Measure both read improvement and write impact.
- Protect execution plans as potentially sensitive diagnostic data.
- Retain plans and performance evidence for regression comparison.
Practice Exercise
Analyze and optimize query plans for the online learning platform developed in the previous lessons.
Requirements
- Create representative learners, courses, enrollments, lessons, quizzes, and attempts.
- Query one learner's active enrollments.
- Query one course's learner roster.
- Query recent quiz attempts using cursor pagination.
- Query learners requiring inactivity reminders.
- Capture estimated plans before adding indexes.
- Capture safe runtime plan evidence.
- Compare estimated and actual rows.
- Identify scans, seeks, lookups, joins, sorts, and filters.
- Identify one non-sargable predicate.
- Rewrite the predicate.
- Add one justified composite index.
- Capture the new plan.
- Measure read and write performance.
- Document whether the change should be retained.
Practice Query
SELECT
e.enrollment_id,
e.enrollment_status,
e.completion_percentage,
c.course_title,
e.enrolled_at
FROM enrollments AS e
INNER JOIN courses AS c
ON c.course_id = e.course_id
WHERE
e.tenant_id = :tenant_id
AND e.learner_id = :learner_id
AND e.enrollment_status = 'active'
ORDER BY
e.enrolled_at DESC,
e.enrollment_id DESC;
Candidate Index
CREATE INDEX ix_enrollments_tenant_learner_status_date_id
ON enrollments
(
tenant_id,
learner_id,
enrollment_status,
enrolled_at DESC,
enrollment_id DESC
);
Capture and compare plan evidence before deciding whether the index is appropriate. Also verify whether the existing uniqueness and foreign-key indexes overlap with the candidate.
Plan-review Template
| Plan Area | Before Change | After Change | Conclusion |
|---|---|---|---|
| Access path | Record scan or seek | Record scan or seek | Explain observed difference |
| Estimated rows | Record estimate | Record estimate | Compare with actual rows |
| Actual rows | Record runtime value | Record runtime value | Explain cardinality accuracy |
| Join strategy | Record operator | Record operator | Evaluate input sizes |
| Sort | Record operation | Record operation | Confirm whether index order helped |
| Reads | Record measurement | Record measurement | Calculate improvement |
| Duration | Record measurement | Record measurement | Compare representative executions |
| Write cost | Record baseline | Record new cost | Decide whether trade-off is acceptable |
Frequently Asked Questions
What is a query plan?
A query plan is a tree of physical database operations used to execute a SQL statement.
What does a query optimizer do?
It evaluates candidate access paths, join orders, join algorithms, and other operations before selecting an execution plan.
What is the difference between estimated and actual plans?
An estimated plan is generated without executing the statement. An actual plan accompanies execution and can include supported runtime counters.
What is cardinality estimation?
Cardinality estimation predicts how many rows each query-plan operation will produce.
Is an index scan always bad?
No. A scan can be efficient when a large portion of the data is required or the input is small.
Is an index seek always fast?
No. A broad seek can process many rows, and a seek repeated many times can perform substantial total work.
What is a key lookup?
It is an additional operation used to retrieve columns that are absent from the selected secondary index.
Why do estimated and actual rows differ?
Possible causes include stale statistics, data skew, parameter sensitivity, correlated predicates, expressions, and other estimation limitations.
What is a query spill?
A spill occurs when a memory-dependent operator uses temporary storage because its available execution memory is insufficient.
Should every missing-index suggestion be implemented?
No. Review overlap, width, write cost, storage, column order, and the wider workload before creating the index.
Can the plan change without changing the SQL?
Yes. Statistics, parameters, schema, indexes, configuration, cache state, and database conditions can influence plan selection.
What comes after query plans?
The next topic is connection pools, which completes the Relational Data Modeling & SQL module.
Key Takeaway
A query plan reveals how the database accesses, joins, filters, sorts, and aggregates data. Read plans as operator trees, compare estimated and actual rows, and focus on total work rather than isolated operator names or displayed percentages. Scans can be appropriate, seeks can still be expensive, and low-cost operations can dominate when repeated many times. Use plans to identify cardinality errors, residual filters, lookups, spills, unsuitable joins, unnecessary sorting, and parameter sensitivity. Make one evidence-based change at a time, measure again with representative data, and include write and concurrency impact in the final decision.