database-indexing-strategy
Design and implement database indexing strategies. Use when creating indexes, choosing index types, or optimizing index performance in PostgreSQL and MySQL.
$ Instalar
git clone https://github.com/aj-geddes/useful-ai-prompts /tmp/useful-ai-prompts && cp -r /tmp/useful-ai-prompts/skills/database-indexing-strategy ~/.claude/skills/useful-ai-prompts// tip: Run this command in your terminal to install the skill
name: database-indexing-strategy description: Design and implement database indexing strategies. Use when creating indexes, choosing index types, or optimizing index performance in PostgreSQL and MySQL.
Database Indexing Strategy
Overview
Design comprehensive indexing strategies to improve query performance, reduce lock contention, and maintain data integrity. Covers index types, design patterns, and maintenance procedures.
When to Use
- Index creation and planning
- Query performance optimization through indexing
- Index type selection (B-tree, Hash, GiST, BRIN)
- Composite and partial index design
- Index maintenance and monitoring
- Storage optimization with indexes
- Full-text search index design
Index Types and Use Cases
PostgreSQL Index Types
B-tree Indexes (Default):
-- Standard equality and range queries
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_orders_created_at ON orders(created_at DESC);
-- Composite indexes for multi-column queries
CREATE INDEX idx_orders_user_status
ON orders(user_id, status)
WHERE cancelled_at IS NULL;
Hash Indexes:
-- Exact match queries only
CREATE INDEX idx_product_sku USING hash ON products(sku);
-- Good for equality lookups on large text fields
CREATE INDEX idx_uuid_hash USING hash ON sessions(session_id);
BRIN Indexes (Block Range):
-- For large tables with monotonically increasing columns
CREATE INDEX idx_events_timestamp USING brin ON events(created_at)
WITH (pages_per_range = 128);
-- Excellent for time-series data
CREATE INDEX idx_logs_timestamp USING brin
ON application_logs(log_timestamp);
GiST & GIN Indexes:
-- GiST for spatial data and complex types
CREATE INDEX idx_locations_geom USING gist ON locations(geom);
-- GIN for JSONB and array columns
CREATE INDEX idx_products_metadata USING gin ON products(metadata);
CREATE INDEX idx_user_tags USING gin ON users(tags);
MySQL Index Types
B-tree Indexes:
-- Standard index for most queries
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_orders_user_created
ON orders(user_id, created_at);
-- Prefix indexes for large columns
CREATE INDEX idx_description_prefix
ON products(description(100));
FULLTEXT Indexes:
-- Full-text search on text columns
CREATE FULLTEXT INDEX idx_products_search
ON products(name, description);
-- Query using MATCH...AGAINST
SELECT * FROM products
WHERE MATCH(name, description) AGAINST('laptop' IN BOOLEAN MODE);
Spatial Indexes:
-- For geographic data
CREATE SPATIAL INDEX idx_locations
ON locations(geom);
Index Design Patterns
Single Column Indexes
PostgreSQL:
-- Filtered index for active records only
CREATE INDEX idx_users_active
ON users(created_at)
WHERE deleted_at IS NULL;
-- Descending order for LIMIT queries
CREATE INDEX idx_posts_published DESC
ON posts(published_at DESC)
WHERE status = 'published';
MySQL:
-- Simple equality lookup
CREATE INDEX idx_users_verified ON users(email_verified);
-- Range queries on numeric columns
CREATE INDEX idx_products_price ON products(price);
Composite Indexes
PostgreSQL - Optimal Ordering:
-- Order: equality columns, then range, then sort
-- Query: WHERE user_id = X AND created_at > Y ORDER BY id
CREATE INDEX idx_optimal_composite
ON orders(user_id, created_at, id);
-- Covering index to eliminate table access
CREATE INDEX idx_covering_orders
ON orders(user_id, status, created_at)
INCLUDE (total, currency);
MySQL - Leftmost Prefix:
-- MySQL uses leftmost prefix matching
-- Can be used by: (user_id), (user_id, status), (user_id, status, created_at)
CREATE INDEX idx_users_complex
ON users(user_id, status, created_at);
-- For queries: user_id + status + created_at
SELECT * FROM orders
WHERE user_id = 1 AND status = 'completed' AND created_at > '2024-01-01';
Partial/Filtered Indexes
PostgreSQL:
-- Only index active products
CREATE INDEX idx_active_products
ON products(category_id)
WHERE active = true;
-- Reduce index size and improve performance
CREATE INDEX idx_not_cancelled_orders
ON orders(user_id, created_at)
WHERE status != 'cancelled';
-- Complex filter conditions
CREATE INDEX idx_vip_orders
ON orders(total DESC)
WHERE total > 10000 AND customer_type = 'vip';
Expression Indexes
PostgreSQL:
-- Index on computed values
CREATE INDEX idx_users_email_lower
ON users(LOWER(email));
-- Enable case-insensitive searches
SELECT * FROM users WHERE LOWER(email) = 'john@example.com';
-- Date extraction indexes
CREATE INDEX idx_orders_year
ON orders(EXTRACT(YEAR FROM created_at));
Index Maintenance
PostgreSQL Index Analysis:
-- Check index size and usage
SELECT schemaname, tablename, indexname,
pg_size_pretty(pg_relation_size(indexrelid)) as size,
idx_scan as scans,
idx_tup_read as tuples_read
FROM pg_stat_user_indexes
ORDER BY pg_relation_size(indexrelid) DESC;
-- Find unused indexes
SELECT schemaname, tablename, indexname
FROM pg_stat_user_indexes
WHERE idx_scan = 0 AND indexrelname NOT LIKE 'pg_toast%';
-- Rebuild fragmented indexes
REINDEX INDEX idx_users_email;
MySQL Index Statistics:
-- Check index cardinality
SELECT object_schema, object_name, count_star
FROM performance_schema.table_io_waits_summary_by_index_usage
WHERE object_schema != 'mysql'
ORDER BY count_star DESC;
-- Update table statistics
ANALYZE TABLE users;
ANALYZE TABLE orders;
Concurrent Index Creation
PostgreSQL - Non-blocking Index Creation:
-- Create index without locking table (PostgreSQL 9.2+)
CREATE INDEX CONCURRENTLY idx_new_column
ON large_table(new_column);
-- Safe for production
REINDEX INDEX CONCURRENTLY idx_products_price;
MySQL - Concurrent Index Creation:
-- MySQL 8.0 supports ALGORITHM=INPLACE with LOCK=NONE
ALTER TABLE users ADD INDEX idx_created (created_at),
ALGORITHM=INPLACE, LOCK=NONE;
-- Check online DDL progress
SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;
Performance Monitoring
PostgreSQL - Index Performance:
-- Top 10 most scanned indexes
SELECT schemaname, tablename, indexname, idx_scan
FROM pg_stat_user_indexes
ORDER BY idx_scan DESC LIMIT 10;
-- Indexes with high read/scan ratio
SELECT indexname, idx_scan, idx_tup_read,
CASE WHEN idx_scan = 0 THEN 0
ELSE ROUND(idx_tup_read::numeric / idx_scan, 2) END as efficiency
FROM pg_stat_user_indexes
WHERE idx_scan > 0
ORDER BY efficiency DESC;
MySQL - Index Statistics:
-- Show table index information
SHOW INDEX FROM products;
-- Check cardinality (distribution)
SELECT * FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = 'your_database'
AND TABLE_NAME = 'products'
ORDER BY SEQ_IN_INDEX;
Index Creation Checklist
- Identify slow queries with EXPLAIN/EXPLAIN ANALYZE
- Check filter columns, JOIN conditions, ORDER BY clauses
- Consider index order (equality → range → sort)
- Use partial indexes to reduce size on large tables
- Include columns for covering indexes
- Monitor index usage after creation
- Drop unused indexes to save space
- Rebuild fragmented indexes periodically
Common Mistakes
❌ Don't create too many indexes (write performance impact) ❌ Don't create indexes without testing first ❌ Don't ignore index size and storage impact ❌ Don't forget to update table statistics after bulk operations ❌ Don't create duplicate indexes
✅ DO create indexes on foreign keys ✅ DO test index impact on INSERT/UPDATE performance ✅ DO use covering indexes for common queries ✅ DO drop unused indexes regularly ✅ DO monitor index fragmentation
Resources
Repository
