Overview
These two techniques refine ordinary B-tree indexes. A covering index lets a query finish without touching the table. A partial index stores entries only for rows you actually search.
How it works
PostgreSQL examples.
-- covering: filter on user_id, return status and total from the index
CREATE INDEX idx_orders_user_cover
ON orders (user_id, created_at) INCLUDE (status, total);
-- partial: only unprocessed jobs are indexed
CREATE INDEX idx_jobs_pending
ON jobs (created_at) WHERE state = 'pending';
EXPLAIN SELECT status, total FROM orders WHERE user_id = 7;
-- expect: Index Only Scan
If ninety-nine percent of jobs are done, the pending index is a hundredth of the size of a full one.
Column order still matters inside the key. Equality columns come first, then the range or sort column, and payload columns go in INCLUDE so they do not affect ordering. Index-only scans also depend on the engine knowing that pages are all visible to current transactions; in PostgreSQL, vacuum maintains this map, so a table with heavy updates may still read the heap. Review the pg_stat_user_indexes counters to find indexes with zero scans and drop them.
Pitfalls
- Assuming the index is used without checking EXPLAIN output.
- Writing a query predicate that does not imply the partial index condition.
- Including large text columns in a covering index.
- Never removing indexes after queries change.
Key takeaways
- Cover hot queries to enable index-only scans.
- Index only the rows you query with partial indexes.
- Confirm usage with query plans and index statistics.
- Prune unused indexes regularly.