Have you ever launched a query that felt like it was taking forever to complete? I’ve been there. When MySQL databases grow, performance can take a serious hit—and that’s when proper indexing becomes your secret weapon. After diving deep into MySQL optimization over the years, I’ve compiled these absolutely essential MySQL indexing best practices that will transform your database performance. Let me share what I’ve learned through countless hours of testing and implementation.
Think of indexes as the table of contents in a massive book. Without them, MySQL has to flip through every single page (full table scan) to find what you’re looking for. With proper indexing, it jumps straight to the right section, making everything lightning fast.
Indexes create data structures that allow MySQL to locate rows quickly without examining the entire table. This dramatically improves query execution time, especially for large datasets.
I’ve seen developers go index-crazy when they face performance issues, thinking “more indexes = better performance.” That’s completely wrong.
Here’s why you should never index everything:
Instead, focus on indexing columns that appear in WHERE clauses, JOIN conditions, and ORDER BY statements—places where performance gains matter most.
This decision makes a massive difference in query performance:
/* For queries like this */
SELECT * FROM customers WHERE status = 'active' AND country = 'Canada';
/* Create a composite index */
CREATE INDEX idx_status_country ON customers(status, country);
Code language: JavaScript (javascript)
/* For queries like this */
SELECT * FROM customers WHERE status = 'active' OR country = 'Canada';
/* Create individual indexes */
CREATE INDEX idx_status ON customers(status); CREATE INDEX idx_country ON customers(country);
Code language: JavaScript (javascript)
Getting this wrong can mean the difference between millisecond responses and multi-second queries.
The order of columns in a composite index is absolutely critical. MySQL can only use a composite index efficiently if the query uses a leftmost prefix of the indexed columns.
For example, if you have INDEX(a,b,c)
:
a
a AND b
a AND b AND c
b
or just c
Best practice: Place higher cardinality columns (those with more unique values) first in your index. This dramatically improves selectivity, allowing MySQL to filter out more rows earlier in the execution process.
/* Good order (assuming user_id has higher cardinality than status) */
CREATE INDEX idx_user_status ON orders(user_id, status);
/* Less effective order */
CREATE INDEX idx_status_user ON orders(status, user_id);
Code language: JavaScript (javascript)
💡: Explore more MySQL Best Practices!
Never assume MySQL is using your indexes! Always check with the EXPLAIN command:
EXPLAIN SELECT * FROM customers
WHERE last_name = 'Smith' AND city = 'New York';
Code language: JavaScript (javascript)
Pay special attention to these fields in the output:
If you see “NULL” in the key column, MySQL is doing a full table scan—a red flag for large tables.
This seemingly minor detail can have significant performance implications. MySQL treats NULL values specially in indexes, requiring additional checking during queries.
Compare these two approaches:
/* Less efficient */
CREATE TABLE customers (
id INT PRIMARY KEY,
email VARCHAR(100) NULL,
INDEX (email)
);
/* More efficient */
CREATE TABLE customers (
id INT PRIMARY KEY,
email VARCHAR(100) NOT NULL DEFAULT '',
INDEX (email)
);
Code language: PHP (php)
The second approach avoids NULL checks during index operations, improving performance. When columns genuinely need NULL values, keep them—but don’t default to nullable columns without good reason.
A covering index is my absolute favorite MySQL optimization trick. It contains all columns needed for a query, allowing MySQL to satisfy the query entirely from the index without touching the table data.
/* Query that can benefit from a covering index */
SELECT user_id, created_at FROM orders WHERE status = 'completed';
/* Create a covering index */
CREATE INDEX idx_status_cover ON orders(status, user_id, created_at);
Code language: JavaScript (javascript)
When MySQL uses a covering index, you’ll see “Using index” in the EXPLAIN output’s Extra column. These queries can be 2-10x faster than regular indexed queries.
Columns with few unique values (low cardinality) often don’t benefit from indexing. Examples include:
MySQL will often ignore indexes on these columns anyway, as full table scans might be faster. Exception: If these low-cardinality columns are always used with other, more selective columns in queries, include them in composite indexes.
Sometimes MySQL’s optimizer makes wrong choices. In these rare cases, you can suggest or force index usage:
/* Suggest an index */
SELECT * FROM products USE INDEX(idx_category_price)
WHERE category_id = 5 AND price > 100;
/* Force an index */
SELECT * FROM products FORCE INDEX(idx_category_price)
WHERE category_id = 5 AND price > 100;
Code language: PHP (php)
Use FORCE INDEX sparingly—it’s usually better to trust MySQL’s optimizer or redesign your indexes.
MySQL 8.0 introduced functional indexes and more powerful index filtering. This lets you index only specific rows that matter for your queries:
/* Index only active products */
CREATE INDEX idx_price_active ON products(price) WHERE status = 'active';
Code language: JavaScript (javascript)
This creates smaller, more efficient indexes focused on the data you actually query.
Over time, as you update and delete data, indexes become fragmented, reducing their efficiency. Regular maintenance keeps indexes optimized:
/* Analyze table to update statistics */
ANALYZE TABLE customers;
/* Optimize table to rebuild indexes */
OPTIMIZE TABLE customers;
Code language: JavaScript (javascript)
For InnoDB tables, consider scheduling these operations during low-traffic periods.
If you need to index expressions or function results, MySQL 8.0+ allows generated columns that can be indexed:
/* Add generated column and index it */
ALTER TABLE users
ADD COLUMN email_domain VARCHAR(255)
GENERATED ALWAYS AS (SUBSTRING_INDEX(email, '@', -1)) STORED,
ADD INDEX idx_email_domain (email_domain);
/* Now you can query efficiently by domain */
SELECT * FROM users WHERE email_domain = 'gmail.com';
Code language: PHP (php)
This approach is much more efficient than using function-based WHERE clauses that can’t use indexes.
Every database is unique. A perfect indexing strategy depends on:
Use these tools to measure real-world performance:
Don’t blindly follow indexing rules—test different approaches with your actual workload and measure the results.
Proper MySQL indexing best practices aren’t just performance tricks—it’s absolutely essential for scalable applications. The difference between good and bad indexing can be the difference between a responsive application and one that frustrates users with delays.
Start implementing these tips today, and watch your database performance transform. Remember that indexing is a balance between read performance, write performance, and resource usage—find the sweet spot for your specific needs. Refer to official documentation to learn about indexing more in depth.
Have you implemented any of these MySQL indexing techniques? What kind of performance improvements did you see? Share your experiences in the comments!
Learn how to integrate service workers in React, Next.js, Vue, and Angular with practical code examples and production-ready implementations for modern web applications.
Master the essential service worker caching strategies that transform web performance. Learn Cache-First, Network-First, and Stale-While-Revalidate patterns with practical examples that'll make your apps blazingly…
Master the intricate dance of service worker states and events that power modern PWAs. From registration through installation, activation, and termination, understanding the lifecycle unlocks…
This website uses cookies.
View Comments
"MySQL optimizer may discard/not select those indexes which aren’t in same order as in ‘WHERE’ condition." This is not true. Also, "you should try to put higher cardinality columns first" this isn't true either - it doesn't matter. If you have INDEX(a,b,c) and WHERE a=1 AND b=2 the index-prefix is used but that shouldn't be confused with order of columns in an index.
I said there 'MAY BE', based on my own experience. I have experienced issue without with ordering, could be because of large complex join queries. Also, it is clearly mentioned on mysql multi-column indexing documentation that if we have an index(a,b,c), trying to use condition in "b ='' AND c=''" won't work, where "a='' AND b= ''" will work.
About your second complain, May be you don't know about this yet then, read this stackoverflow thread. Also see the last section on this article, which I updated recently with link to a presentation I got chance to attend. See slide 12. Both evidence clearly support this concept. It makes sense as total amount of search is reducing down.
That's exactly what I mentioned - thus my "index-prefix" or "leftmost" wording. index(a,b,c) will work for (a), (a,b) and (a,b,c) to be used in WHERE clause. You should tweak this sentence "MySQL optimizer may discard/not select those indexes which aren’t in same order as in ‘WHERE’ condition.” because there's no such thing as "in same order as in WHERE condition" - "WHERE a=1 AND b=2" and "WHERE b=2 AND a=1" are identical.
Also, however there's a chance that high-cardinality-column-first strategy brings better selectivity, it's more important to consider if low cardinality columns should be in index in the first place. In general, you shouldn't index too low cardinality columns like (male, female) unless you're doing covering index, and column ordering in index is negligible compared to impact of to-index-or-not decision.
There's one very important thing that column ordering in index matters - ORDER BY optimization. If you have index(a,b,c) and a query WHERE a=1 AND b=2 ORDER BY c, it uses index to scan in this order, but if the query was WHERE a=1 AND b=2 ORDER BY a, it cannot use index for sorting and end up with filesort. If I were to mention anything about ordering of compound-index ordering, this is it.
It's also worth noting that primary keys are always included in indexes on InnoDB - index(a) is actually index(a,pk), so WHERE a=1 ORDER BY pk is always fast, and SELECT pk FROM tbl WHERE a=1 is a covering index query.