Execution Plans: Scan vs Seek
Read an execution plan to see how the database runs your query, and learn the difference between a table scan, an index scan and an index seek.
You wrote a query and it's slow. Why? Guessing is useless — the database will tell you exactly what it did if you look at the execution plan. Every performance tip in this chapter should be checked against the plan: never guess, measure.
Definition
An execution plan is the step-by-step strategy the database's query optimizer chooses to run a query — which tables and indexes it reads, in what order, how it joins them and how it sorts or aggregates — together with the estimated (or actual) cost of each step.
- Query optimizer — the part of the engine that considers many possible strategies and picks the cheapest one, using statistics about your data.
- Estimated plan — what the optimizer plans to do (query not run).
- Actual plan — what really happened, including the real row counts (query is run).
Table Scan vs Index Scan vs Index Seek
| Operation | What it does | Pages read to find ONE player in 1,000,000 rows (illustrative) |
|---|---|---|
| Table Scan | reads every row of the table (heap) | ~1,000,000 reads |
| Index Scan | reads the whole index, start to end (smaller than the table) | ~10,000 reads |
| Index Seek | walks the B-Tree: root → index page → data page | ~3 reads |
Each step down this list is roughly 10–100× less work. Your goal for selective queries: SEEK > SCAN.
Note: A scan is not always bad. If a query needs most of the table (e.g.
SUM(runs)of every row), a scan is the cheapest plan. Scans are a problem when you want a few rows and still read everything.
How to see a plan
| Database | How |
|---|---|
| SQL Server (SSMS) | Ctrl + L = estimated plan · Ctrl + M = include actual plan, then run · read the graphical plan right to left |
| SQL Server (text) | SET STATISTICS IO ON; → logical reads per table · SET STATISTICS TIME ON; → CPU / elapsed time |
| PostgreSQL / MySQL | EXPLAIN / EXPLAIN ANALYZE |
| SQLite (playground) | EXPLAIN QUERY PLAN |
sql
-- SQL Server: measure pages read and time
SET STATISTICS IO ON;
SET STATISTICS TIME ON;
SELECT *
FROM players
WHERE name = 'Hardik Pandya';
-- Messages tab: Table 'players'. Scan count 1, logical reads 1250 ...Common SQL Server plan operators
| Operator | Meaning |
|---|---|
| Table Scan | full read of a heap |
| Clustered Index Scan | full read of a clustered table |
| Clustered Index Seek / Index Seek | B-Tree navigation straight to the rows ✅ |
| Key Lookup / RID Lookup | extra hop from a non-clustered index to the full row (expensive when repeated many times) |
| Nested Loops | join: for each outer row, look up matches — best for small inputs |
| Hash Match | join or aggregate using a hash table — best for large unsorted inputs |
| Merge Join | join two inputs already sorted on the join key |
| Sort | an explicit sort (ORDER BY, DISTINCT, …) — often costly |
Example — the same query before and after an index (playground)
sql
EXPLAIN QUERY PLAN
SELECT *
FROM players
WHERE name = 'Virat Kohli';| detail |
|---|
| SCAN players |
sql
CREATE INDEX IX_players_name ON players (name);
EXPLAIN QUERY PLAN
SELECT *
FROM players
WHERE name = 'Virat Kohli';| detail |
|---|
| SEARCH players USING INDEX IX_players_name (name=?) |
In SQLite words: SCAN = read every row, SEARCH = index seek, jump to the row. In a 50,000-row table this is the difference between reading the whole table and reading a handful of pages.
Example — a plan reveals a hidden sort
sql
EXPLAIN QUERY PLAN
SELECT DISTINCT team
FROM players
ORDER BY team;| detail |
|---|
| SCAN players |
| USE TEMP B-TREE FOR DISTINCT |
The second line is an extra sorting/de-duplication step that only exists because of DISTINCT. Plans like this are how you discover that a "harmless" keyword costs work (performance tip #2).
Example — a join plan
sql
EXPLAIN QUERY PLAN
SELECT t.team_name, p.name
FROM players AS p
INNER JOIN teams AS t
ON p.team = t.team;| detail |
|---|
| SCAN p |
| SEARCH t USING INDEX sqlite_autoindex_teams_1 (team=?) |
How it works: this is a nested loop: scan every player, and for each one seek the matching team through the primary-key index on teams.team. That's why indexing join columns matters (tip #10).
The plan-driven workflow
- Write the query.
- Read the plan — scan or seek? lookups? sorts? nested loops over big inputs?
- Apply a tip — add an index, rewrite, filter earlier.
- Compare plans (and logical reads / time) — is it really faster?
Common mistakes
- ❌ Judging speed by one run on your laptop. ✅ Compare plans and logical reads; timings vary with cache.
- ❌ Panicking at every scan. ✅ Scans are right when you need most rows.
- ❌ Reading the SSMS graphical plan left to right. ✅ Data flows right to left; the leftmost operator returns the result.
Interview tip
Q: What is the difference between an index scan and an index seek? A: An index seek uses the B-Tree to jump directly to the matching key range, reading only a few pages. An index scan reads every leaf page of the index from start to end. Seeks are what you want for selective filters; a scan usually means there's no usable index, the filter isn't sargable, or the query needs most of the rows.
Key points to remember
- An execution plan is the strategy the query optimizer chose: which indexes, join methods, sorts, and the cost of each step.
- Table Scan reads every row, Index Scan reads the whole index, Index Seek walks the B-Tree to the rows — aim for SEEK on selective queries.
- In SSMS: Ctrl+L = estimated plan, Ctrl+M = actual plan; SET STATISTICS IO/TIME ON shows logical reads and time.
- Key/RID Lookups, Sorts and large Nested Loops are common warning signs in a plan.
- In the playground (SQLite) use EXPLAIN QUERY PLAN: SCAN = full read, SEARCH = index seek.
- Always measure before and after a change — the plan, reads and time are your evidence.
Try it yourself
SQL Server: measure reads and time
-- Turn on statistics, then run the query (see the Messages tab)
SET STATISTICS IO ON;
SET STATISTICS TIME ON;
SELECT *
FROM players
WHERE name = 'Hardik Pandya';
-- In SSMS press Ctrl+M (actual plan) before running
-- to see Table Scan vs Index Seek graphically.SQL Server-specific — run this in SQL Server (SSMS / Azure Data Studio).
Try it: SCAN before, SEARCH after
Runs on a fresh IPL practice database in your browser · edit the query and press Ctrl + Enter
A plan reveals the hidden DISTINCT sort
Runs on a fresh IPL practice database in your browser · edit the query and press Ctrl + Enter
Practice: read a join plan
Runs on a fresh IPL practice database in your browser · edit the query and press Ctrl + Enter
Revision Flashcards
Tap a card to flip it and see the answer.
