Chapter 14: B-Tree Internals — The Data Structure Behind Indexes
The B-Tree (and its variant B+Tree) is the most important data structure in databases. Understanding it explains why indexes are fast, why column order in composite indexes matters, and why some queries can't use indexes.
Why B-Trees? (Not Binary Trees)
A binary search tree has 2 children per node. For 1 million keys, that's ~20 levels deep = 20 disk reads per lookup. Too slow.
A B-Tree has hundreds of children per node (because each node = one disk page = 8KB, fitting many keys). For 1 million keys with a branching factor of 500, that's only 3 levels deep = 3 disk reads.
B+Tree Structure (What Databases Actually Use)
Databases use B+Trees (a variant where all data is in leaf nodes):
Operations
Lookup: O(log n)
// Find key=42:
// 1. Root: 42 > 30, 42 < 60 → go to second child
// 2. Internal: find leaf containing 42
// 3. Leaf: binary search within leaf → found at position 3
// 4. Follow row pointer → read actual row from heap
// Total: 3-4 page reads
Range Scan: O(log n + k)
// Find all keys between 35 and 78:
// 1. B-tree lookup to find leaf containing 35 (3 reads)
// 2. Follow leaf links → → → until key > 78
// 3. Collect all row pointers along the way
// This is why leaf nodes are linked!
Insert: O(log n)
// Insert key=40:
// 1. Find correct leaf (same as lookup)
// 2. Insert into leaf (maintaining sorted order)
// 3. If leaf is full → SPLIT into two leaves
// → promote middle key to parent
// → if parent full, split recursively up
Composite Index Column Order
A composite index on (country, city, name) sorts like a phone book: first by country, then by city within each country, then by name within each city.
A composite index on (A, B, C) can be used for queries on: A, (A,B), or (A,B,C). It CANNOT be used for queries on just B, just C, or (B,C). The leftmost columns must be present. This is the most common indexing mistake.
Index Types Beyond B-Tree
| Type | Best For | Example |
|---|---|---|
| B-Tree | Equality, range, sorting (default) | WHERE age > 25 |
| Hash | Equality only (faster than B-tree for =) | WHERE id = 123 |
| GiST | Geometric, full-text, ranges | WHERE location @> point |
| GIN | Arrays, JSONB, full-text search | WHERE tags @> '{grpc}' |
| BRIN | Large tables with natural ordering | Time-series data (sorted by timestamp) |
Clustered vs Non-Clustered
| Aspect | Clustered (InnoDB PK) | Non-Clustered (Secondary) |
|---|---|---|
| Leaf contains | Full row data | Key + pointer to row |
| Table order | Rows stored in index order | Separate structure |
| Count per table | One (the PK) | Many |
| Range scan | Very fast (sequential I/O) | May need random I/O to fetch rows |
- B+Tree: balanced tree with high branching factor → 3-4 levels for millions of rows
- Leaf nodes are linked for efficient range scans
- Composite index column order determines which queries can use it (leftmost prefix rule)
- Different index types for different query patterns (B-tree, Hash, GIN, GiST, BRIN)
- Clustered index = rows stored in index order (InnoDB PK). One per table.