Chapter 17: Indexes
This is the chapter where we learn how to turn a slow, crawling database into a super-fast, responsive one!
Imagine your books table has 1 million rows. You run this query:
|
0 1 2 3 4 5 6 |
SELECT * FROM books WHERE title = 'The Room on the Roof'; |
Without any help, the database has to scan every single row — like searching for a book in a library by checking every shelf one by one. That’s slow — especially as data grows.
An index is like the alphabetical index at the back of a book — it lets the database jump straight to the right page (row) without reading everything.
Today we’ll cover:
- CREATE INDEX – How to create indexes
- DROP INDEX – How to remove them
- Clustered vs. Non-clustered indexes – The two main types
- When to use indexes – And when NOT to (very important!)
Let’s use our bookshop database.
1. CREATE INDEX – Building Indexes
Syntax (basic):
|
0 1 2 3 4 5 6 7 |
CREATE INDEX index_name ON table_name (column_name); |
Example 1 – Simple index on title
|
0 1 2 3 4 5 6 7 |
CREATE INDEX idx_books_title ON books (title); |
Now when you run:
|
0 1 2 3 4 5 6 |
SELECT * FROM books WHERE title = 'The Room on the Roof'; |
The database uses the index → much faster!
Example 2 – Index on multiple columns (composite index)
|
0 1 2 3 4 5 6 7 |
CREATE INDEX idx_books_author_price ON books (author_id, price); |
This helps queries like:
|
0 1 2 3 4 5 6 7 |
SELECT * FROM books WHERE author_id = 1 AND price < 300; |
Example 3 – Unique index (enforces uniqueness)
|
0 1 2 3 4 5 6 7 |
CREATE UNIQUE INDEX idx_books_isbn ON books (isbn); |
This is like adding a UNIQUE constraint but as an index — faster lookups + enforces no duplicates.
Example 4 – Index on a function (functional index – advanced)
|
0 1 2 3 4 5 6 7 |
CREATE INDEX idx_books_title_lower ON books (LOWER(title)); |
Helps case-insensitive searches:
|
0 1 2 3 4 5 6 |
SELECT * FROM books WHERE LOWER(title) = 'the room on the roof'; |
Pro Tip: Index names usually start with idx_ or ix_ + table + column(s).
2. DROP INDEX – Removing Indexes
Very simple — and safe (just removes the index, not the data).
Syntax:
|
0 1 2 3 4 5 6 |
DROP INDEX index_name ON table_name; |
Example:
|
0 1 2 3 4 5 6 |
DROP INDEX idx_books_title ON books; |
Safe version (MySQL):
|
0 1 2 3 4 5 6 |
DROP INDEX IF EXISTS idx_books_title ON books; |
Why drop indexes?
- They take up disk space
- They slow down INSERT, UPDATE, DELETE (because index must be updated too)
- You no longer need them for old queries
3. Clustered vs. Non-clustered Indexes – The Two Main Types
| Feature | Clustered Index | Non-clustered Index |
|---|---|---|
| How many per table? | Only 1 per table | Many allowed |
| How data is stored | Table rows are physically sorted by the indexed column | Separate structure (like a phone book) pointing to rows |
| Primary Key | Usually automatically clustered on PRIMARY KEY | Can be non-clustered |
| Speed | Faster for range queries & sorted data | Slightly slower than clustered |
| Example | PRIMARY KEY (book_id) → rows physically ordered by book_id | Index on title → separate B-tree pointing to rows |
Real example in our books table:
|
0 1 2 3 4 5 6 7 8 9 10 11 |
-- Primary key is usually clustered automatically ALTER TABLE books ADD PRIMARY KEY (book_id); -- Clustered index on book_id -- Non-clustered index on title CREATE INDEX idx_books_title ON books (title); |
When you query by book_id → super fast (clustered) When you query by title → still fast (non-clustered), but not as fast as clustered
Important (MySQL InnoDB):
- PRIMARY KEY is always clustered
- If no PRIMARY KEY → first UNIQUE index becomes clustered
- If none → hidden row ID becomes clustered
4. When to Use Indexes – The Golden Rules (2026 Style)
Create an index when:
- You frequently filter on a column (WHERE author_id = 3)
- You frequently join on a column (ON b.author_id = a.author_id)
- You frequently sort on a column (ORDER BY price DESC)
- You have foreign keys (many databases auto-index them, but good to check)
- Columns with high cardinality (many unique values — e.g., title, email, isbn)
- You run range queries (price BETWEEN 200 AND 400, published_year > 2000)
Do NOT index (or remove index) when:
- Column has low cardinality (few unique values — e.g., gender: ‘M’/’F’, status: ‘active’/’inactive’)
- Table is very small (< 1000 rows — index may be slower)
- You do lots of INSERT/UPDATE/DELETE on the table (indexes slow them down)
- Column is updated very frequently (each update rebuilds index)
- You already have too many indexes (each index takes space and slows writes)
Golden Rule for Indexes: Index columns that appear in WHERE, JOIN ON, ORDER BY, GROUP BY clauses — but only the ones you actually use!
How to check if index is used?
|
0 1 2 3 4 5 6 |
EXPLAIN SELECT * FROM books WHERE title = 'The Room on the Roof'; |
Look for type: ref or index (good) vs type: ALL (bad — full table scan)
Full Realistic Example Workflow
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
-- 1. Add a primary key (automatically clustered) ALTER TABLE books ADD PRIMARY KEY (book_id); -- 2. Add useful non-clustered indexes CREATE INDEX idx_books_author_id ON books (author_id); -- For joins & filters CREATE INDEX idx_books_price ON books (price); -- For price ranges CREATE INDEX idx_books_title ON books (title); -- For title searches CREATE INDEX idx_books_published_year ON books (published_year); -- For year filters -- 3. Composite index for common query pattern CREATE INDEX idx_books_author_price ON books (author_id, price); -- 4. Later, if we don't need one anymore DROP INDEX idx_books_price ON books; -- 5. Check usage EXPLAIN SELECT title, price FROM books WHERE author_id = 1 AND price < 300; |
