Explore plans starting at ₹699/mo →
Database

Database Indexing Strategies: A Complete Guide to Query Performance

S
ServerRaja
11 min read
#Database#Tutorial#Scaling#PostgreSQL#Cloud VPS#Guide#Best Practices#Performance#MySQL
Database Indexing Strategies: A Complete Guide to Query Performance

The Power of Proper Indexing

Indexes are the single most impactful tool for improving database query performance. A well-chosen index can reduce a query's execution time from seconds to milliseconds, while a missing or poorly designed index can bring your application to its knees under load. For applications hosted on ServerRaja Cloud VPS, where resources are allocated and billed based on usage, efficient indexing also translates directly into cost savings by reducing CPU and IOPS consumption.

This guide covers indexing strategies for both MySQL (InnoDB) and PostgreSQL, with practical examples you can apply to your cloud database today.

How Database Indexes Work

An index is a data structure—typically a B-tree—that maintains a sorted reference to rows in a table, allowing the database engine to locate rows without scanning the entire table. Think of it like the index at the back of a textbook: instead of reading every page to find a topic, you look it up in the index and jump directly to the right page.

-- Create a basic B-tree index on a frequently queried column
CREATE INDEX idx_orders_customer_id ON orders (customer_id);

-- Verify the index exists -- PostgreSQL \di+ idx_orders_customer_id

-- MySQL SHOW INDEX FROM orders WHERE Key_name = 'idx_orders_customer_id'; ```

Types of Indexes

B-Tree Indexes (Default) B-tree indexes are the workhorse of relational databases. They work well for equality comparisons (`=`), range comparisons (`BETWEEN`, `>`, `<`), and `ORDER BY` clauses.

-- Useful for queries like:
SELECT * FROM orders WHERE order_date BETWEEN '2025-01-01' AND '2025-12-31';
SELECT * FROM customers WHERE email = '[email protected]';

Composite (Multi-Column) Indexes When queries filter on multiple columns, a composite index can be far more efficient than separate single-column indexes. The key principle is column order: place the most selective column first, or match the column order used in your most common `WHERE` clauses.

-- Composite index for common query pattern
CREATE INDEX idx_orders_status_date ON orders (status, order_date);

-- This index efficiently serves: SELECT * FROM orders WHERE status = 'shipped' AND order_date > '2025-06-01'; -- And also: SELECT * FROM orders WHERE status = 'pending'; -- But NOT: SELECT * FROM orders WHERE order_date > '2025-06-01'; -- leftmost prefix rule ```

The leftmost prefix rule means MySQL (InnoDB) uses a composite index only if the query includes the leftmost column(s) of the index. PostgreSQL has a more flexible optimizer that can sometimes use index skip scans.

Partial Indexes (PostgreSQL) Partial indexes index only a subset of rows, reducing index size and maintenance cost.

-- Only index active orders
CREATE INDEX idx_active_orders ON orders (customer_id, order_date)
  WHERE status = 'active';

-- This index is tiny and fast for: SELECT * FROM orders WHERE status = 'active' AND customer_id = 42; ```

GIN Indexes (PostgreSQL) Generalized Inverted Indexes are ideal for full-text search, JSONB queries, and array containment.

-- GIN index for JSONB columns
CREATE INDEX idx_products_attrs ON products USING GIN (attributes);

SELECT * FROM products WHERE attributes @> '{"brand": "Samsung", "color": "black"}';

-- GIN index for full-text search CREATE INDEX idx_articles_fts ON articles USING GIN (to_tsvector('english', title || ' ' || body));

SELECT * FROM articles WHERE to_tsvector('english', title || ' ' || body) @@ to_tsquery('english', 'cloud & hosting'); ```

GiST Indexes (PostgreSQL) Generalized Search Tree indexes support complex data types including geometric data, ranges, and full-text search with ranking.

-- GiST index for range queries
CREATE INDEX idx_events_period ON events USING GiST (tstzrange(start_time, end_time));

SELECT * FROM events WHERE tstzrange(start_time, end_time) && tstzrange('2025-07-01', '2025-07-31'); ```

Hash Indexes Hash indexes support only equality comparisons but can be faster than B-tree for exact-match lookups.

-- PostgreSQL hash index
CREATE INDEX idx_sessions_token ON sessions USING HASH (token);

-- MySQL InnoDB uses hash indexes internally in the adaptive hash index -- but does not allow explicit hash index creation on disk ```

Analyzing Query Performance

Before creating indexes, always analyze the actual query execution plan.

-- PostgreSQL
EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM orders WHERE customer_id = 100 AND status = 'shipped';

-- MySQL EXPLAIN ANALYZE SELECT * FROM orders WHERE customer_id = 100 AND status = 'shipped'; ```

Look for sequential scans (Seq Scan in PostgreSQL, full table scan in MySQL) on large tables—these are candidates for indexing.

Common Indexing Mistakes to Avoid

**Over-indexing:** Every index adds write overhead. Each INSERT, UPDATE, and DELETE must maintain every index on the table. A table with 10 indexes will have significantly slower writes than a table with 2.

-- Check unused indexes in PostgreSQL
SELECT schemaname, relname, indexrelname, idx_scan
FROM pg_stat_user_indexes
WHERE idx_scan = 0
ORDER BY pg_relation_size(indexrelid) DESC;

**Indexing low-cardinality columns:** Columns like `status` or `is_active` with very few distinct values rarely benefit from standalone indexes. They work better as the second column in a composite index.

**Ignoring index bloat:** Over time, indexes can become bloated with dead tuples, especially in PostgreSQL.

-- Rebuild a bloated index in PostgreSQL
REINDEX INDEX idx_orders_customer_id;

-- Online reindexing (PostgreSQL 12+) REINDEX INDEX CONCURRENTLY idx_orders_customer_id; ```

Index Maintenance Best Practices

Schedule regular index maintenance to keep performance optimal:

1. Monitor index usage statistics weekly 2. Remove unused indexes to reduce write overhead 3. Rebuild bloated indexes during maintenance windows 4. Use `ANALYZE` to update table statistics after bulk loads 5. Consider covering indexes that include all columns needed by a query to avoid table lookups entirely

-- Covering index example
CREATE INDEX idx_orders_covering ON orders (customer_id, status)
  INCLUDE (order_date, total_amount);

-- This query can be answered entirely from the index SELECT order_date, total_amount FROM orders WHERE customer_id = 100 AND status = 'shipped'; ```

Conclusion

Effective indexing is part science and part art. Start by examining your slowest queries with `EXPLAIN ANALYZE`, create targeted indexes for your most common access patterns, and maintain them regularly. On ServerRaja Cloud VPS with NVMe storage, the IOPS savings from proper indexing compound quickly, letting you serve more users on the same infrastructure.

Database Indexing Strategies Guide | ServerRaja