Database Indexing Strategies: When and How to Index
Indexes can make queries fast or they can kill write performance. Here's how to think strategically about database indexing for your application.
Jason Overmier
Innovative Prospects Team
Indexes are a double-edged sword. Done right, they make queries fast. Done wrong, they slow writes, consume storage, and may not even help reads. Understanding when and how to index requires thinking about your specific query patterns, not just adding indexes to every column.
What Indexes Do
| Aspect | Without Index | With Index |
|---|---|---|
| Read performance | Full table scan | Direct lookup |
| Write performance | Fast (just append) | Slower (update index) |
| Storage | Table data only | Additional index structures |
| Memory usage | Lower | Higher (cached indexes) |
When to Index
Clear Indexing Wins
| Query Type | Why Index Helps |
|---|---|
| Exact lookup | WHERE user_id = 123 |
| Range queries | WHERE created_at > '2024-01-01' |
| Sorting | ORDER BY created_at DESC |
| JOIN keys | JOIN orders ON user_id = 123 |
When Indexes May Not Help
| Scenario | Why Index Doesn’t Help |
|---|---|
| Small tables | Full scan is fast enough |
| High write, low read | Index slows writes without read benefit |
| Wildcard LIKE | WHERE name LIKE '%son%' |
| OR conditions | WHERE a = 1 OR b = 2 |
| Computed columns | WHERE LOWER(email) = 'x' |
When Indexes Hurt
| Scenario | Why It’s a Problem |
|---|---|
| Many indexes | Write performance degrades with each index |
| Unused indexes | Waste storage, memory, write capacity |
| Overlapping indexes | Redundant indexes waste resources |
| Wide indexes | Large indexes consume significant storage |
Index Types
Single-Column Index
CREATE INDEX idx_user_email ON users(email);
Use when: Filtering or sorting on a single column.
Trade-off: Minimal overhead, targeted improvement.
Composite Index
CREATE INDEX idx_user_status_created ON users(status, created_at);
Use when: Queries filter on multiple columns together.
Order matters: Left-to-right prefix matching applies.
| Query | Uses Index? |
|---|---|
WHERE status = 'active' | Yes (leftmost prefix) |
WHERE created_at > '2024-01-01' | No (skipped leading column) |
WHERE status = 'active' AND created_at > '2024-01-01' | Yes (both columns) |
Covering Index
CREATE INDEX idx_user_email_name ON users(email) INCLUDE (name, created_at);
-- Or in MySQL:
CREATE INDEX idx_user_email_name ON users(email, name, created_at);
Use when: Query can be satisfied entirely from the index.
Benefit: Avoids table lookup entirely.
Partial Index
CREATE INDEX idx_active_users ON users(email) WHERE status = 'active';
Use when: Only care about a subset of rows.
Benefit: Smaller index, faster queries for common case.
Unique Index
CREATE UNIQUE INDEX idx_user_email ON users(email);
Use when: Column should have unique values.
Benefit: Enforces uniqueness, efficient lookups.
Query Analysis Workflow
Step 1: Identify Slow Queries
| Method | What to Look For |
|---|---|
| Slow query logs | Queries taking >100ms |
| EXPLAIN output | Sequential scans, high row estimates |
| Application monitoring | Endpoints with high latency |
| Database metrics | Cache hit ratios |
Step 2: Understand the Query
| Question | Why It Matters |
|---|---|
| What columns are filtered? | Determines which columns to index |
| What’s the selectivity? | High selectivity = more index benefit |
| What’s the sort order? | May need index for ORDER BY |
| What columns are returned? | May benefit from covering index |
| How often does this query run? | Frequency determines optimization priority |
Step 3: Choose Index Strategy
| Query Type | Index Strategy |
|---|---|
| Single column equality | Single-column index |
| Multiple column equality | Composite index (order by selectivity) |
| Range + equality | Composite index (equality first, then range) |
| Sort only | Index on sort column |
| Full table with small result | Covering index |
Step 4: Verify Improvement
| Method | What to Check |
|---|---|
| EXPLAIN before/after | Index scan vs sequential scan |
| Query timing | Actual execution time |
| Production metrics | Real-world performance impact |
Common Indexing Mistakes
| Mistake | Symptom | Fix |
|---|---|---|
| Over-indexing | Slow writes, wasted storage | Remove unused indexes |
| Wrong column order | Index not used for queries | Reorder composite index columns |
| Missing indexes on foreign keys | Slow JOINs | Index all foreign key columns |
| Not considering selectivity | Index doesn’t help | Understand query selectivity |
| Ignoring write load | Inserts getting slower | Balance read/write trade-offs |
Maintenance
Monitor Index Usage
-- PostgreSQL
SELECT * FROM pg_stat_user_indexes WHERE schemaname = 'public';
-- Find unused indexes
SELECT * FROM pg_stat_user_indexes WHERE idx_scan = 0;
Rebuild When Needed
| When to Rebuild | Why |
|---|---|
| Index bloat | Deleted rows leave index gaps |
| Corruption | Index becomes corrupted |
| Major version upgrade | Query patterns change significantly |
Regular Review
| Review Task | Frequency |
|---|---|
| Check slow query logs | Weekly |
| Review index usage | Monthly |
| Analyze query patterns | Quarterly |
| Audit index bloat | Quarterly |
Database indexing requires ongoing attention, not just initial setup. If you’re designing a database schema and need guidance on indexing strategy, book a consultation. We’ll help you design indexes that match your actual query patterns.