Indexing and Storage
6 pages · ~4 min✓ Reviewed
Builds on ER Model and Normalization. Next up: Transactions and ACID.
Pages and Records: Storage Basics
1. Pages and Records
- Page = fixed-size disk block, the smallest unit the DB reads or writes
- Often 4-16 KB: 8 KB PostgreSQL, 16 KB InnoDB
- One row lookup reads the whole page
- Record lives in a slot; slot directory maps slot number to byte offset
- Record ID = (page number, slot number)
- Record ID(page 7, slot 2)
- Page 7one disk read
- Slot directoryslot 2 → offset
- Recordbytes at offset
2. Heap Files and Record Layout
- Heap file = pages in no particular order
- No index means a full scan of every page
| Fixed-length | Variable-length (VARCHAR) | |
|---|---|---|
| Find field | simple offset calculation | needs the slot directory |
| Too big? | no | can overflow into extra pages |
3. Cost Model
- Speed is measured in page I/Os, not rows
- Fewer pages touched = faster query
- Heap scan of 1,000 pages = 1,000 I/Os, even for one row
Count pages, not rows
A 1-row lookup still costs a whole page read. Without an index, it costs every page.
Primary vs Secondary and Clustered vs Non-Clustered
1. Primary vs Secondary
- Primary index: built on the primary key, unique and not null
- Secondary index: built on any other column, can be non-unique
2. Clustered vs Non-Clustered
| Clustered | Non-clustered | |
|---|---|---|
| Leaf holds | the full row, in key order | key + pointer to the row |
| Pointer | none, row is in the leaf | heap: record ID; InnoDB: primary key |
| Per table | only one | many |
| Range scan | adjacent pages, sequential | random row fetches |
- Secondary indexfind the key
- Primary keyread from leaf
- Clustered indexsecond traversal
- Rowfound
3. Range Scans
- Clustered range: read adjacent pages in order
- Non-clustered range: one random fetch per matching row
InnoDB secondary lookup
It costs two traversals: secondary index to get the primary key, then the clustered index to get the row.
B+ Trees: The Default Index Structure
1. Shape of a B+ Tree
- Internal nodes hold keys + child pointers only
- Leaves hold all the data or row pointers
- Leaves form a doubly linked list: find start key, walk sideways for a range
- Node = one page, fan-out in the hundreds: millions of rows, only 3-4 levels
2. Lookup Cost and Splits
- Lookup = O(log_f N) page reads, f = fan-out
- 3 levels = 3 page I/Os, root usually cached
Insert into a full leaf
- 1Leaf full
- 2Split in twotwo half-full leaves
- 3Push separator upparent gains a key
- 4Cascade to roottree stays balanced
3. Composite Index (a,b,c)
sql
CREATE INDEX idx_abc ON t (a, b, c);leftmost-prefix rule
| Filter | Uses idx_abc? |
|---|---|
a | yes |
a + b | yes |
a + b + c | yes |
b alone | no |
Leftmost prefix
Skip a and the index cannot help. Put the column you always filter on first.
Hash Indexes vs B+ Trees
1. Hash Index
- Hash function on the key picks a bucket
- Equality
WHERE id = 5: O(1) average - No range, ORDER BY or prefix: hashing destroys key order
- Collisions: chaining in bucket or overflow pages
- Too many collisions: lookups slide toward O(n)
2. Hash vs B+ Tree
| Hash | B+ tree | |
|---|---|---|
= | O(1), faster | O(log N) |
< > BETWEEN | no | yes |
| ORDER BY | no | yes |
LIKE 'abc%' | no | yes |
3. In Practice
sql
CREATE INDEX idx_id ON t USING hash (id);PostgreSQL
- MySQL InnoDB: adaptive hash index, built automatically in memory
- PostgreSQL:
USING hash; B+ tree is the default in both
4. Interview Point
- Why can't a hash index do BETWEEN? Hashing destroys key order, so neighbours are not adjacent
- When pick hash over B+ tree? Equality-only lookups; it is faster for
= - Why can a hash index slow down? Heavy collisions push lookups toward O(n)
Remember
Equality only: hash. Range or sort: B+ tree.
Index Maintenance Cost, Mistakes and Cheat Sheet
1. What a write costs
| Statement | Table | Indexes touched |
|---|---|---|
| INSERT / DELETE | written | every index |
| UPDATE indexed column | written | that index: entry moves |
| UPDATE unindexed column | written | none |
- 5 indexes = about 6 structures written per row
2. Common mistakes
- Index on every column: slower writes, wasted memory and disk, planner may pick wrong
- Low cardinality (boolean, gender): planner prefers a full scan anyway
- UUIDv4 clustered key: random inserts cause page splits and fragmentation
- Sequential keys just append to the last page
Index only what you use
Columns in WHERE, JOIN and ORDER BY only.
3. Cheat sheet
| Situation | Pick |
|---|---|
| Equality only | hash |
| Range or sort | B+ tree |
| Clustered index | one per table |
| Composite index | leftmost prefix |
| Read-heavy table | more indexes |
| Write-heavy table | fewer indexes |
Check yourself
An index on (a, b, c) exists. Does WHERE b = 5 AND c = 2 use it for a seek?
- No: the leftmost-prefix rule needs
afirst. - Without
a, the sorted order doesn't help. - Expect a scan.
InnoDB, secondary index on email. How many tree traversals does SELECT * WHERE email = 'x' take?
- Two.
- Secondary index gives the primary key.
- Clustered index then gives the row.
Inserts slow down after switching the clustered key from an auto-increment id to UUIDv4. Why?
- Random keys land in random pages.
- Full pages split, causing fragmentation.
- Sequential keys just append to the last page.
A hash index exists on id. Is WHERE id BETWEEN 5 AND 9 fast?
- No: hashing destroys key order.
- Hash serves only
=. - Use a B+ tree and walk the linked leaves.