Chapter 13: Indexes & Performance
Indexes are the secret sauce that makes MySQL queries blazing fast — especially when you have thousands or millions of rows.
Today we’ll learn:
- What indexes really are (with a real-life analogy)
- How to create and drop indexes
- Types: Primary, Unique, Composite
- The magic EXPLAIN statement (how to see if MySQL is using your index)
- When to use indexes (and when NOT to — very important!)
Let’s first prepare our students table with lots of data so we can see the performance difference.
Run this to create a bigger table (copy-paste all):
|
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
-- Create a fresh table with many rows DROP TABLE IF EXISTS students_large; CREATE TABLE students_large ( student_id INT AUTO_INCREMENT PRIMARY KEY, full_name VARCHAR(100) NOT NULL, email VARCHAR(100) UNIQUE, phone VARCHAR(15), city VARCHAR(50), join_date DATE, is_active BOOLEAN DEFAULT TRUE ); -- Insert 1000+ sample rows (just run once) INSERT INTO students_large (full_name, email, phone, city, join_date) SELECT CONCAT('Student_', LPAD(n, 4, '0'), ' ', CASE WHEN n % 5 = 0 THEN 'Sharma' WHEN n % 5 = 1 THEN 'Patel' WHEN n % 5 = 2 THEN 'Joshi' WHEN n % 5 = 3 THEN 'Khan' ELSE 'Gupta' END) AS full_name, CONCAT('student', n, '@example.com') AS email, CONCAT('9', LPAD(n % 100000000, 9, '0')) AS phone, CASE WHEN n % 4 = 0 THEN 'Mumbai' WHEN n % 4 = 1 THEN 'Pune' WHEN n % 4 = 2 THEN 'Delhi' ELSE 'Bangalore' END AS city, DATE_SUB(CURDATE(), INTERVAL n DAY) AS join_date FROM (SELECT a.N + b.N * 10 + c.N * 100 + 1 AS n FROM (SELECT 0 AS N UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) a, (SELECT 0 AS N UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) b, (SELECT 0 AS N UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) c LIMIT 1500) numbers; |
Now we have ~1500 students — perfect to see the difference indexes make!
1. What are Indexes? (Real-life analogy)
Imagine your coaching class has 1500 students and their records are stored in a huge register book — pages numbered 1 to 1500.
If someone asks: “Show me all students from Mumbai”
Without an index: You have to read every single page from 1 to 1500 → very slow!
With an index: You have a separate small notebook (the index) that says:
- Mumbai students are on pages: 3, 7, 12, 15, 19, 25, 28, …
- Pune students on pages: 1, 4, 8, 11, 16, …
Now you just look at the small notebook, find the page numbers, and jump directly to those pages → super fast!
In MySQL:
- An index is a separate data structure (usually B-Tree) that stores sorted copies of one or more columns + pointers to the actual rows.
- Indexes make SELECT, WHERE, JOIN, ORDER BY, GROUP BY much faster.
- But: Indexes slow down INSERT, UPDATE, DELETE (because the index must be updated too).
2. CREATE INDEX & DROP INDEX
Syntax – Simple index on one column
|
0 1 2 3 4 5 6 |
CREATE INDEX idx_city ON students_large(city); |
Syntax – Drop index
|
0 1 2 3 4 5 6 |
DROP INDEX idx_city ON students_large; |
Example – Create index on city and see speed difference
Without index (slow):
|
0 1 2 3 4 5 6 |
SELECT * FROM students_large WHERE city = 'Mumbai'; |
With index (fast):
|
0 1 2 3 4 5 6 7 8 |
CREATE INDEX idx_city ON students_large(city); SELECT * FROM students_large WHERE city = 'Mumbai'; |
You’ll feel the difference immediately on large tables!
3. Primary vs Unique vs Composite Indexes
| Type | What it is | Can have NULL? | Multiple per table? | Automatically created? | Example |
|---|---|---|---|---|---|
| PRIMARY KEY | Unique + Not Null + One per table | No | Only one | Yes (when you define PK) | student_id |
| UNIQUE INDEX | Ensures no duplicate values | Yes (only one NULL allowed) | Many | No | |
| COMPOSITE (Multi-column) | Index on 2+ columns together | Depends | Many | No | city + join_date |
Example – Create UNIQUE index on email
|
0 1 2 3 4 5 6 |
CREATE UNIQUE INDEX idx_email ON students_large(email); |
Example – Composite index (very powerful for queries using multiple columns)
|
0 1 2 3 4 5 6 |
CREATE INDEX idx_city_join ON students_large(city, join_date); |
This index is perfect for queries like:
|
0 1 2 3 4 5 6 7 |
SELECT * FROM students_large WHERE city = 'Mumbai' AND join_date > '2025-01-01'; |
4. EXPLAIN Statement – See if MySQL Uses Your Index!
EXPLAIN shows MySQL’s query execution plan — the most important tool for performance tuning!
Syntax:
|
0 1 2 3 4 5 6 |
EXPLAIN SELECT ... ; |
Example – Without index
|
0 1 2 3 4 5 6 |
EXPLAIN SELECT * FROM students_large WHERE city = 'Mumbai'; |
Look for:
- type: ALL → Full table scan (bad!)
- rows: 1500 → Scanning all rows
After creating index
|
0 1 2 3 4 5 6 7 8 |
CREATE INDEX idx_city ON students_large(city); EXPLAIN SELECT * FROM students_large WHERE city = 'Mumbai'; |
Now you’ll see:
- type: ref or range → Using index (good!)
- key: idx_city → Index name
- rows: ~375 (much less than 1500)
Best possible:
- type: const or eq_ref → Super fast
5. When to Use Indexes (Golden Rules)
Use indexes when:
- Columns frequently used in WHERE clauses
- Columns used in JOIN conditions
- Columns used in ORDER BY or GROUP BY
- High selectivity (many different values, e.g., email, city, join_date)
- Primary keys & foreign keys (MySQL auto-creates indexes for them)
Do NOT index:
- Columns with very low selectivity (e.g., gender: only ‘M’/’F’ — only 2 values)
- Very small tables (< 1000 rows — no benefit)
- Columns updated very frequently (index maintenance cost)
- Columns with lots of NULL values (unless you really need it)
Best practices:
- Index foreign keys always
- Use composite indexes for queries with multiple conditions (leftmost prefix rule)
- Don’t over-index (each index takes space and slows writes)
- Use EXPLAIN before & after adding indexes
That’s it for Chapter 13! 🎉 You now understand how to make queries 10–100× faster!
Homework for today (do it right now – very important!) Run these and paste the EXPLAIN output (copy-paste the table) for each:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
-- 1. Without index on city EXPLAIN SELECT * FROM students_large WHERE city = 'Mumbai'; -- 2. Create index and check again CREATE INDEX idx_city ON students_large(city); EXPLAIN SELECT * FROM students_large WHERE city = 'Mumbai'; -- 3. Composite index example CREATE INDEX idx_city_join ON students_large(city, join_date); EXPLAIN SELECT * FROM students_large WHERE city = 'Delhi' AND join_date > '2025-01-01'; -- 4. Try a query that uses the composite index partially EXPLAIN SELECT * FROM students_large WHERE city = 'Pune'; |
