Database Indexes: Single-Key, Compound, and Covering Indexes
Interactive simulator modeling Full Table Scans vs Single-Key, Compound, and Covering Indexes (Index-Only Scan with 0 heap fetches), plus the column order trap.
The Cookbook Index: Full Table Scan vs. Single, Compound & Covering Indexes
Imagine searching for "Chocolate Lava Cake" in an 800-page cookbook:
• No Index (Full Table Scan): You flip page-by-page from page 1 to 800. Exhausting and slow!
• Single Index: The back index lists Cake → Page 312. You jump there in 1 second, but still have to read the heavy page to find the oven temperature (Heap Fetch).
• Compound Index (Category, Dish): Sorted by [Desserts > Cakes > Lava Cake]. Instant jump! But if you search only by cooking time without category, the alphabetical list is useless (Leading Column Trap).
• Covering Index: The index card itself prints the oven temp right next to the title! You get your answer without ever opening the heavy cookbook (Index-Only Scan, 0 Heap Fetches!).
Track 1: B-Tree Index & Planner
In-Memory Traversal // RAM Buffer Cache
(user_id=42, status='COMPLETED') + Payload: total_amount=$189.50Index Only Scan using idx_covering on orders (cost=0.42..4.44 rows=1 width=24) (actual time=0.082..0.084 rows=1 loops=1) Index Cond: ((user_id = 42) AND (status = 'COMPLETED'::text)) Heap Fetches: 0 Buffers: shared hit=3 Planning Time: 0.112 ms Execution Time: 0.204 ms
Track 2: Physical Table Heap
8KB Page Blocks // Disk I/O Subsystem
Index exists on (user_id, status), but query filtered on status alone without the leading user_id prefix. The engine cannot binary-search the tree and must scan the entire heap!
By storing total_amount in the leaf node via INCLUDE, the database answered your query entirely inside memory without reading a single page from disk! Heap Fetches: 0. Latency: 0.2ms!
Potential Fixes for Full Table Scans & Slow Queries
Append high-frequency read columns to index leaf nodes. Turns slow table heap scans into pure Index-Only Scans with 0 disk fetches.
Always place exact equality filters (tenant_id, status) before range or ordering predicates (created_at) to prevent b-tree branch abandonments.
Don't index millions of historical rows. Add WHERE status IN ('PENDING', 'PROCESSING') to keep the active index in fast RAM cache.
Deep-Dive Technical Reading
Explore the engineering theory, mathematical foundations, and production Go implementations behind this simulator.
Experiencing architectural bottlenecks in production?
From connection starvation and p99 latency spikes to Kafka consumer lag, index optimization, and algorithm profiling.