Clustered vs Non-Clustered Index
Learn how a clustered index physically sorts the table, how a non-clustered index points to rows, and how to create both (plus composite indexes).
Every serious SQL Server table has to answer one question first: in what order are the rows physically stored? The clustered index answers that. Every other "shortcut" you add afterwards — on name, team, match_date — is a non-clustered index. Knowing the difference is one of the most common SQL interview questions.
Definition
A clustered index sorts and stores the table's rows physically in the order of the index key. Its leaf level is the data itself, so a table can have only one clustered index.
A non-clustered index is a separate sorted structure that holds the key values plus a pointer (row locator) to the real row. The table's data is not reordered, so a table can have many non-clustered indexes.
Unpacking the words:
- Physically sorted — rows for id 1, 2, 3 … really sit next to each other on the data pages. That is why only one order (one clustered index) is possible — just like a book can be printed in only one page order.
- Leaf level is the data — when the B-Tree search finishes, you are already standing on the row. No extra hop.
- Pointer / row locator — in a non-clustered index the leaf holds the key and a locator. On a heap the locator is a RID (file:page:slot, e.g.
3:3); on a clustered table it is the clustered key value. Following that pointer is called a lookup (RID Lookup or Key Lookup).
Clustered index — search path
Table players with a clustered index on id, finding WHERE id = 11:
| Hop | Page | What SQL reads |
|---|---|---|
| 1 | Root 1:300 | 1–8 → 1:200, 9–16 → 1:201 → go to 1:201 |
| 2 | Index 1:201 | 9–12 → 1:102, 13–16 → 1:103 → go to 1:102 |
| 3 | Data page 1:102 | 9 KL Rahul · 10 R. Jadeja · 11 Hardik Pandya · 12 Rashid Khan |
3 pages read instead of thousands. Rows are stored sorted by id, so range queries such as WHERE id BETWEEN 5 AND 12 are also fast — SQL finds 5 and reads forward.
Non-clustered index — search path
A non-clustered index on name is a separate list sorted A→Z, each entry with a pointer:
| name (sorted) | pointer (page:slot) |
|---|---|
| David Warner | 2:1 |
| Hardik Pandya | 3:3 |
| Jos Buttler | 1:3 |
| KL Rahul | 3:1 |
| MS Dhoni | 1:4 |
| Virat Kohli | 1:1 |
For WHERE name = 'Hardik Pandya', SQL seeks the name index, finds pointer 3:3, then jumps to data page 3, slot 3 to fetch the full row. That extra jump is why a non-clustered index is slightly slower to read than a clustered one.
Clustered vs Non-Clustered — side by side
| Clustered index | Non-clustered index | |
|---|---|---|
| Definition | Physically sorts and stores the rows | Separate structure with pointers to the data |
| Number per table | ONE | Many (up to 999 in SQL Server) |
| Read performance | Faster (leaf = data) | Slower — an extra pointer hop (lookup) |
| Write performance | Slower — rows may need re-ordering / page splits | Faster — data order is unaffected |
| Storage | More storage-efficient (no copy of the data) | Needs extra storage space |
| Best use case | Unique, ever-increasing column; range queries (e.g. id, dates) | Columns used in WHERE and JOINs, exact matches, foreign keys |
Syntax
sql
CREATE [CLUSTERED | NONCLUSTERED] INDEX index_name
ON table_name (column1 [ASC | DESC], column2 [ASC | DESC], ...);| Part | Meaning |
|---|---|
CLUSTERED | physically sort the table by these columns (only one allowed) |
NONCLUSTERED | separate index — this is the default if you write neither |
index_name | convention: IX_table_column (e.g. IX_players_team) |
(column1, column2) | one column = simple index; several = composite index |
ASC / DESC | sort order inside the index (default ASC) |
sql
-- Clustered: rows physically sorted by id
CREATE CLUSTERED INDEX IX_players_id ON players (id);
-- Non-clustered: a separate index on team
CREATE NONCLUSTERED INDEX IX_players_team ON players (team);
-- Composite + sort order (NONCLUSTERED by default)
CREATE INDEX IX_players_country_runs ON players (country ASC, runs DESC);Note: In SQL Server, declaring a
PRIMARY KEYautomatically creates a unique clustered index on it (unless a clustered index already exists). That's why most tables are clustered on their id.
Composite index and the leftmost-prefix rule
A composite index is an index on two or more columns. Its entries are sorted by the first column, then by the second inside each first-column value, and so on.
IX_players_country_runs (country, runs DESC) is like a phone book sorted by country, then by runs. So it helps:
| Query filter | Can it seek on the index? |
|---|---|
WHERE country = 'India' | ✅ yes — leading column |
WHERE country = 'India' AND runs > 300 | ✅ yes — both columns, in order |
WHERE runs > 300 (no country) | ❌ no seek — the leading column is missing |
Example — composite index also removes a sort (playground)
sql
CREATE INDEX IX_players_country_runs ON players (country ASC, runs DESC);
SELECT name, runs
FROM players
WHERE country = 'India'
ORDER BY runs DESC;| name | runs |
|---|---|
| Virat Kohli | 750 |
| Rohit Sharma | 500 |
| MS Dhoni | 350 |
| Jasprit Bumrah | 20 |
Without the index, EXPLAIN QUERY PLAN shows SCAN players plus USE TEMP B-TREE FOR ORDER BY (an extra sort step). With it, the plan is just SEARCH players USING INDEX IX_players_country_runs (country=?) — the rows already come out in runs DESC order, so no sort is needed.
Common mistakes
- ❌ Trying to create a second clustered index. ✅ Only one per table — make the others non-clustered.
- ❌ Clustering on a wide, random, frequently-updated column (e.g. a GUID or
name). ✅ Cluster on a narrow, unique, static, ever-increasing key such asINT IDENTITY— every non-clustered index copies the clustered key, so a wide key bloats them all. - ❌ Putting the wrong column first in a composite index. ✅ Put the column you filter with
=most often first.
SQL Server vs MySQL / PostgreSQL
- SQL Server: you choose
CLUSTERED/NONCLUSTEREDexplicitly. - MySQL (InnoDB): the table is always clustered on the primary key; every secondary index is non-clustered. No keyword needed.
- PostgreSQL: tables are heaps; all indexes are effectively non-clustered (
CLUSTERre-sorts once but is not maintained). - SQLite (playground):
CLUSTERED/NONCLUSTEREDkeywords are not supported — writeCREATE INDEX.
Interview tip
Q: Why can a table have only one clustered index but many non-clustered ones? A: A clustered index is the physical order of the rows, and data can be stored in only one order. A non-clustered index is just a separate sorted copy of some columns with pointers back to the rows, so you can create many of them.
Key points to remember
- A clustered index physically sorts and stores the rows by the key; its leaf level IS the data, so only one is allowed per table.
- A non-clustered index is a separate sorted structure with pointers (RID or clustered key) to the rows; a table can have many.
- Clustered = faster reads and range queries; non-clustered = extra lookup hop but cheaper writes and great for WHERE/JOIN columns.
- NONCLUSTERED is the default when you just write CREATE INDEX; a PRIMARY KEY creates a clustered index by default in SQL Server.
- A composite index is sorted by its first column, then the next — it only helps queries that filter on the leading column(s).
- Choose a narrow, unique, static, ever-increasing clustered key (like INT IDENTITY).
Try it yourself
SQL Server: clustered and non-clustered syntax
-- Clustered: rows physically sorted by id (only ONE per table)
CREATE CLUSTERED INDEX IX_players_id
ON players (id);
-- Non-clustered: separate structure with pointers (many allowed)
CREATE NONCLUSTERED INDEX IX_players_team
ON players (team);
-- Composite + sort order (NONCLUSTERED is the default)
CREATE INDEX IX_players_country_runs
ON players (country ASC, runs DESC);SQL Server-specific — run this in SQL Server (SSMS / Azure Data Studio).
Try it: same idea in the playground (composite index)
Runs on a fresh IPL practice database in your browser · edit the query and press Ctrl + Enter
Composite index without the leading column
Runs on a fresh IPL practice database in your browser · edit the query and press Ctrl + Enter
Practice: build a composite index on innings
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.
