If you've ever had a query drag on forever and thought there has to be a faster way, you're absolutely right. Indexes are one of the easiest and most effective ways to speed up data retrieval in SQL.
In this post, we will go through:
- What indexes are
- Why we use them
- Different types of indexes
- Rowstore vs columnstore
- When to use which type
What Is an Index?
An index in SQL works much like the index in a textbook, it helps the system find what you are looking for quickly without reading every row. Without indexes, the database must scan the whole table every time you run a query with a filter.
Indexes are created on one or more columns and allow the engine to look things up much faster. However, they come with a cost, so it's important to understand when and how to use them.
βοΈ Why Use Indexes?
Advantages:
- Speeds up
SELECT
queries significantly - Helps with sorting using
ORDER BY
- Improves performance on
JOIN
andGROUP BY
Things to watch out for:
- Slower write operations like
INSERT
,UPDATE
,DELETE
as the index has to be updated as well - Extra storage usage
Use indexes to improve reads, but be careful if your database is write-heavy.
Common Types of Indexes
1. Single-column index
Use this when you are filtering or sorting using just one column.
Syntax:
CREATE INDEX 'index_name'
ON 'table_name' ('column_name');
Use case:
SELECT * FROM 'table_name' WHERE 'column_name' = 'value';
2. Composite index (multi-column)
Use this when you often filter or sort using two or more columns together.
Syntax:
CREATE INDEX 'index_name'
ON 'table_name' ('first_column', 'second_column');
The order of the columns matters. This index helps:
SELECT * FROM 'table_name' WHERE 'first_column' = X AND 'second_column' = Y;
But not:
SELECT * FROM 'table_name' WHERE 'second_column' = Y;
3. Unique index
This prevents duplicate values in one or more columns.
Syntax:
CREATE UNIQUE INDEX 'index_name'
ON 'table_name' ('column_name');
Used for things like usernames or email addresses where duplicates should not exist.
4. Primary key index
Automatically created when you define a primary key on a table. It ensures values are unique and not null.
Example:
CREATE TABLE 'table_name' (
'column_name' INT PRIMARY KEY
);
You do not need to create this one manually β the database will do it for you.
5. Clustered index
A clustered index defines the physical order of rows on disk. Most databases only allow one clustered index per table.
Syntax:
CREATE CLUSTERED INDEX 'index_name'
ON 'table_name' ('column_name');
Best for:
- Sorting
- Range queries
- Queries like:
SELECT * FROM 'table_name' WHERE 'column_name' BETWEEN A AND B ORDER BY 'column_name';
6. Non-clustered index
This is a separate structure that points to the actual rows. You can have several non-clustered indexes per table.
Syntax:
CREATE NONCLUSTERED INDEX 'index_name'
ON 'table_name' ('column_name');
Good for:
- Lookups and filtering
- Exact match queries
π§± Rowstore vs π Columnstore Indexes
These terms describe how data is stored and retrieved β and they have very different use cases.
Rowstore index
This is the default. Data is stored and indexed row by row.
Use rowstore indexes for:
- Transactional systems
- Systems with lots of inserts and updates
- Traditional queries like:
SELECT * FROM 'table_name' WHERE 'column_name' = 'value';
Columnstore index
Data is stored column by column rather than row by row. It is highly compressed and optimised for aggregation.
Syntax (SQL Server):
CREATE CLUSTERED COLUMNSTORE INDEX 'index_name'
ON 'table_name';
Use columnstore indexes for:
- Data warehousing
- Dashboards
- Analytics like:
SELECT 'region', AVG('amount')
FROM 'table_name'
GROUP BY 'region';
Columnstore indexes are not ideal for frequent updates or point lookups β they shine in large-scale reporting.
What is INCLUDE in an Index?
Sometimes you filter using one set of columns, but also want to retrieve other columns in the result. That is where INCLUDE
comes in.
Hereβs a common problem:
SELECT 'customer_id'
FROM 'table_name'
WHERE 'country' = 'UK' AND 'score' = 200;
You might think an index on ('country', 'score')
would be perfect here. But if the index does not contain 'customer_id'
, the database still has to go back to the main table to fetch it.
If you want the index to cover the query fully, you can include 'customer_id'
in the index.
Syntax:
CREATE NONCLUSTERED INDEX 'index_name'
ON 'table_name' ('country', 'score')
INCLUDE ('customer_id');
Use INCLUDE
when:
- The columns in the
SELECT
list are not part of the filtering but still need to be returned - You want to avoid the extra step of looking up the base table
To Summarize: When to Use Which Index
Single-column index
When your queries filter or sort by one specific column
Composite index
When you regularly filter or sort by the same combination of two or more columns
Unique index
When you need to enforce uniqueness (such as usernames or email addresses)
Primary key index
Always β this ensures each row has a unique identifier
Clustered index
When you frequently sort or filter by a column range, like dates or prices
Non-clustered index
When you need to speed up lookups on other columns not in the primary key
Rowstore index
When you are building transactional applications with frequent inserts and updates
Columnstore index
When building reporting or analytical queries over large volumes of data