Database Performance

Database Index Design Patterns: Composite, Covering, and Partial Indexes

Master database index design: composite index column order, covering indexes with INCLUDE, partial indexes for filtered queries, functional indexes, and unused index auditing.

JusDB Team
May 14, 2025
Updated June 20, 2026
5 min read

The right indexes make queries orders of magnitude faster; the wrong ones waste disk space and slow down writes. Here are the most important index design patterns for MySQL and PostgreSQL.

Composite Index Column Order

Columns in a composite index should be ordered: equality first, then range, then sort.

sql
-- Query: WHERE status = 'active' AND created_at > '2025-01-01' ORDER BY created_at DESC

-- Good: equality (status) first, then range/sort (created_at)
CREATE INDEX idx_orders_status_created ON orders (status, created_at DESC);

-- Bad: range column first — wastes the index for the equality filter
CREATE INDEX idx_orders_bad ON orders (created_at, status);

Covering Indexes (Index-Only Scans)

sql
-- Include all columns needed by the query to avoid heap lookups
-- Query: SELECT user_id, total FROM orders WHERE status = 'pending'

-- PostgreSQL: use INCLUDE for non-searchable columns
CREATE INDEX idx_orders_status_cover ON orders (status) INCLUDE (user_id, total);

-- MySQL: add to the index key itself
CREATE INDEX idx_orders_status_cover ON orders (status, user_id, total);

Partial Indexes

sql
-- PostgreSQL: index only active orders (much smaller, faster)
CREATE INDEX idx_orders_active ON orders (created_at)
  WHERE status = 'active';

-- Only 2% of orders may be active — index is 50x smaller than full index

Functional Indexes

sql
-- PostgreSQL: case-insensitive email lookups
CREATE INDEX idx_users_email_lower ON users (lower(email));
SELECT * FROM users WHERE lower(email) = lower('User@Example.com');

-- MySQL: expression index (MySQL 8.0+)
CREATE INDEX idx_users_email_lower ON users ((lower(email)));

Find Missing Indexes

sql
-- PostgreSQL: tables with sequential scans
SELECT schemaname, relname, seq_scan, idx_scan,
       seq_scan - idx_scan AS missing_idx_approx
FROM pg_stat_user_tables
WHERE seq_scan > idx_scan AND n_live_tup > 10000
ORDER BY seq_scan DESC
LIMIT 10;

-- MySQL: check slow query log for full table scans
-- Or use pt-query-digest to find top offenders

Find Unused Indexes

sql
-- PostgreSQL: indexes with zero scans since last stats reset
SELECT schemaname, relname, indexrelname,
       idx_scan, pg_size_pretty(pg_relation_size(indexrelid)) AS size
FROM pg_stat_user_indexes
WHERE idx_scan = 0 AND schemaname = 'public'
ORDER BY pg_relation_size(indexrelid) DESC;
Tip: Reset index statistics after major schema changes: SELECT pg_stat_reset(); Then wait 2 weeks before dropping unused indexes — low-frequency queries may not have run yet.

Key Takeaways

  • Composite indexes: equality columns first, then range, then sort columns
  • Covering indexes eliminate heap lookups — use INCLUDE in PostgreSQL
  • Partial indexes are dramatically smaller for filtered queries (e.g., active records only)
  • Audit unused indexes quarterly — each unused index costs write overhead and disk space

JusDB Can Help

Index design is one of the highest-impact performance optimizations. JusDB can audit your indexes, identify missing ones, and remove costly unused indexes.

Share this article

JusDB Team

Official JusDB content team

Keep reading

SQL Server Wait Stats: A Diagnostic Playbook for Slow Queries

Read SQL Server wait stats like a senior DBA: the four DMV sources, the eight wait types that cover 95% of incidents (PAGEIOLATCH, LCK_M, CXPACKET, WRITELOG…), and the remediation for each. A 30-minute diagnostic workflow from page to plan.

SQL Server12 minMay 27, 2026
Read

InnoDB Architecture Explained (2026): Buffer Pool, Redo Log & Production Tuning

Deep dive into InnoDB storage engine internals. Understand buffer pool, redo log, undo log, change buffer, and adaptive hash index for expert-level MySQL optimization.

MySQL16 minMay 13, 2026
Read

MySQL 8.4 Parallel DDL: innodb_parallel_read_threads & innodb_ddl_threads Tuning

Leverage MySQL 8.4 InnoDB parallel DDL for faster schema changes. Learn parallel index creation, online DDL improvements, and reduced maintenance windows.

MySQL8 minMay 13, 2026
Read