The B-Tree (and its variant, the B+Tree) is the default index structure in almost every relational database. This file explains why that structure was chosen, how it’s shaped, and how MySQL, PostgreSQL, SQL Server, and Oracle each implement it slightly differently.
A reporting dashboard filters orders by a date range:
SELECT * FROM orders
WHERE order_date BETWEEN '2026-01-01' AND '2026-01-31';
A hash index could resolve order_date = X in O(1), but it cannot answer
a range query at all — hash structures have no concept of “next
value.” B+Trees preserve sorted order, so a range scan is a single
sequential walk once the starting point is found. This is why B+Trees,
not hash tables, are the default index structure in production databases.
Databases need a structure that supports equality lookups, range scans, and ordered traversal, all while staying balanced as data grows and minimizing disk I/O (each tree level read is typically one disk page read). B-Trees satisfy all three; B+Trees improve on them for range scans specifically by chaining leaf nodes together.
BETWEEN, >, < on timestamps)LIKE 'Sm%' prefix search)ORDER BY id LIMIT 50 OFFSET 100)B-Tree: a balanced tree where every node holds both keys and data (or data pointers), and internal nodes participate in the search.
B+Tree: only leaf nodes hold data; internal nodes hold keys used purely for navigation. Crucially, leaf nodes are linked together in a sorted linked list, so once you reach the first matching leaf, a range scan is a straight walk forward — no need to revisit internal nodes.
Nearly every production RDBMS index (MySQL InnoDB, PostgreSQL default
btree access method, SQL Server clustered/non-clustered indexes, Oracle
default indexes) is a B+Tree despite frequently being called “B-Tree” in
documentation and tooling.
-- MySQL: BTREE is the default and typically doesn't need to be stated
CREATE INDEX idx_orders_order_date
ON orders (order_date)
USING BTREE;
-- PostgreSQL: btree is also the default access method
CREATE INDEX idx_orders_order_date
ON orders USING btree (order_date);
USING BTREE is explicit but redundant in MySQL for InnoDB tables —
InnoDB only supports BTREE and (separately) full-text/spatial indexes;
the clause matters more when working with the MEMORY engine, which also
supports HASH.USING btree follows the same logic — stating it is
optional but self-documenting. [ 50 | 90 ] <- root (internal node)
/ | \
[10|30] [60|75] [100|120] <- internal nodes
/ | \ / | \ / | \
leaf leaf leaf ... <- leaf nodes (linked →)
Leaf nodes: [5,8]→[10,22,29]→[35,41]→[60,68]→[75,81]→[100,110]→...
─────────────────────────────────────────────────►
sorted, linked — a range scan walks this chain directly
Point lookup (order_date = '2026-01-15'):
root → internal node → leaf node → found, stop.
O(log n) disk reads (tree height, typically 3-4 levels
even at hundreds of millions of rows)
Range scan (order_date BETWEEN '2026-01-01' AND '2026-01-31'):
root → internal node → first matching leaf
→ follow leaf-to-leaf links until out of range
O(log n) to find start + O(k) to walk k matching rows
Tree height stays small even at huge scale because each node holds many keys (determined by page size — typically hundreds of keys per 16KB InnoDB page), so a B+Tree over 100 million rows is usually only 3-4 levels deep. This is why index lookups stay fast as tables grow — the cost grows logarithmically, not linearly.
Every level of the tree is stored on disk as pages. A wider tree (more keys per node) means fewer levels and fewer disk reads per lookup — this is why database page sizes (commonly 8-16KB) are tuned for this trade-off rather than left arbitrary.
The optimizer estimates cost partly from expected tree height and page reads. A range query’s estimated cost accounts for both the descent to the starting leaf and the estimated number of leaf pages that must be walked — this is where selectivity estimates (File 07) directly affect whether the optimizer prefers this index or a full scan.
As with all indexing behavior, B-Tree structure is implementation detail, not part of the ANSI standard — the standard only guarantees result correctness, not the retrieval mechanism.
btree access method is a B+Tree variant; leaf
entries store a TID pointing to the row’s physical heap location.CLUSTER has been run
(a one-time physical reorder, not an ongoing guarantee).ORDER BY, not just equality
filters — B+Trees are what make both fast.B+Trees are the default index structure because they support equality lookups, range scans, and ordered traversal efficiently, while staying balanced and shallow (O(log n)) as data grows. Their linked leaf nodes are what make range scans fast — a property hash indexes fundamentally can’t offer.
See 12_PRACTICE_PROBLEMS.md, Intermediate section, Problems 1–3.