What is the difference between a clustered and a non-clustered index?
A clustered index decides the physical order in which table rows are stored, so the table itself is the index and there can be only one. A non-clustered index is a separate structure holding the indexed values plus a pointer back to the row, and a table can have many.
Explain it simply
A telephone directory is sorted by surname. The data itself is in that order, so finding "Sharma" means flipping straight to the S pages, and all the Sharmas sit together. That is a clustered index. A book can only be physically sorted one way, so there is only one.
The index at the back of a textbook is different. It lists topics alphabetically with page numbers. You look up "Normalization", read "page 212", then turn to that page. That is a non-clustered index: a separate list that points to where the real content lives. A book can have several such indexes: by topic, by author name, by figure number.
Example
SQL Server syntax, where the difference is explicit:
CREATE TABLE orders (
order_id int NOT NULL,
customer_id int NOT NULL,
order_date date NOT NULL,
amount decimal(10, 2)
);
-- One clustered index: rows are stored in order_id order
CREATE CLUSTERED INDEX ix_orders_id ON orders (order_id);
-- Many non-clustered indexes: separate structures pointing back to rows
CREATE NONCLUSTERED INDEX ix_orders_customer ON orders (customer_id);
CREATE NONCLUSTERED INDEX ix_orders_date ON orders (order_date) INCLUDE (amount);In SQL Server, a PRIMARY KEY creates a clustered index by default if the table does not have one already.
How it works
| Clustered index | Non-clustered index | |
|---|---|---|
| What it stores | The full rows, in key order | Key values plus a row locator |
| Number per table | One | Many |
| Lookup cost | Find key, data is right there | Find key, then fetch the row (an extra lookup) unless the index covers the query |
| Range queries on the key | Very fast, rows are adjacent | Can be slow if many matching rows are spread out |
| Effect on inserts | Rows inserted in the middle can cause page splits | Each index adds write work |
Both are usually B-tree structures. In a clustered index the leaf level holds the actual rows. In a non-clustered index the leaf level holds the key and a locator: the clustered key value if the table has a clustered index, or a physical row ID if it does not.
How common databases handle this:
- MySQL InnoDB: every table is clustered on its primary key. If there is no primary key, InnoDB uses the first unique NOT NULL index, or creates a hidden row ID. Secondary indexes store the primary key value, so a big primary key makes every secondary index bigger.
- SQL Server: you choose which index is clustered; a table without one is called a heap.
- PostgreSQL: tables are always heaps and all indexes are separate structures, so in SQL Server terms every PostgreSQL index is non-clustered. The
CLUSTERcommand reorders a table by an index once, but the order is not maintained for new rows.
Real-world use
- Cluster an orders table on an increasing
order_idorcreated_at, so new rows go at the end and "orders from last week" reads neighbouring pages. - Add non-clustered indexes on columns you filter by, such as
customer_idor a phone number on a users table. - Use a covering index (
INCLUDEcolumns) for a frequent query, so it never has to visit the table.
Common mistakes
- Saying a table can have several clustered indexes. It has at most one, because rows can be stored in only one order.
- Assuming the primary key and the clustered index are always the same thing. They often are by default, but in SQL Server you can cluster on a different column.
- Choosing a random value, like a random UUID, as the clustered key. Inserts land all over the table and cause page splits and fragmentation.
- Describing PostgreSQL indexes as clustered because of the CLUSTER command.
What to say in the interview
"A clustered index defines the physical order of the rows, so the leaf level of the index is the table itself, and a table can have only one. A non-clustered index is a separate B-tree containing the key and a pointer to the row, so lookups may need an extra step to fetch the row, but a table can have many. In SQL Server the primary key is clustered by default, and in MySQL InnoDB the table is always clustered on the primary key and secondary indexes store that key. PostgreSQL stores tables as heaps, so all its indexes behave like non-clustered ones. I pick a narrow, increasing clustered key to keep inserts cheap and secondary indexes small."
Follow-up questions
- What is a covering index, and what is a key lookup?
- Why is a narrow, increasing primary key preferred in InnoDB?
- What is a page split?
Go deeper
Read these once the basics above feel easy.
When a non-clustered index is used in SQL Server and the query needs columns not in the index, each match triggers a key lookup into the clustered index. For a handful of rows this is cheap; for thousands it is often slower than scanning the clustered index, so the optimizer may ignore the non-clustered index entirely. Adding the needed columns with INCLUDE turns it into a covering index and removes the lookups. In PostgreSQL, the equivalent is an index-only scan, which also depends on the visibility map being up to date, since PostgreSQL must confirm that rows are visible without reading the heap.