Database Indexing
A database index is a data structure that improves the speed of data retrieval operations on a table at the cost of additional storage space and slower writes. It works like a book index — instead of scanning every page, you look up the topic in the index to find the exact page number.
What It Really Means
Without an index, a database must perform a full table scan to find matching rows — reading every single row in the table and checking whether it matches the query predicate. For a table with 10 million rows, this means reading 10 million rows even if only 3 match. An index lets the database jump directly to the matching rows, reducing the work from millions of reads to a handful.
The core trade-off is simple: indexes make reads faster and writes slower. Every time you insert, update, or delete a row, the database must also update every index on that table. A table with 5 indexes requires 6 writes per insert (1 for the table + 5 for the indexes). This is why over-indexing is as dangerous as under-indexing.
There are three major index data structures, each optimized for different workloads: B-Tree (the default in most relational databases), LSM Tree (optimized for write-heavy workloads), and Hash Index (optimized for exact-match lookups). Understanding when to use each is critical for database performance tuning and system design.
How It Works in Practice
B-Tree Index
B-Trees are the most widely used index structure. PostgreSQL, MySQL InnoDB, Oracle, and SQL Server all default to B-Tree indexes. A B-Tree is a self-balancing tree where each node contains multiple keys and pointers, keeping the tree shallow (typically 3-4 levels deep even for billions of rows).
How lookup works: To find a row where email = 'alice@example.com', the database starts at the root node, compares the search key with the keys in the node, follows the appropriate child pointer, and repeats until it reaches a leaf node containing a pointer to the actual data row. This takes O(log n) comparisons — for 100 million rows, that is roughly 4 node reads.
Key property: B-Trees maintain sorted order, so they support both equality queries (WHERE email = ?) and range queries (WHERE created_at BETWEEN ? AND ?). They also support prefix searches (WHERE name LIKE 'Ali%') but not suffix searches (WHERE name LIKE '%ice').
Where B-Trees are used: PostgreSQL, MySQL, Oracle, SQL Server, SQLite — essentially every relational database defaults to B-Tree indexes.
LSM Tree (Log-Structured Merge Tree)
LSM Trees are optimized for write-heavy workloads. Instead of updating an index in-place (like B-Trees), LSM Trees batch writes in memory (memtable) and periodically flush them to sorted, immutable files on disk (SSTables). Background compaction merges these files to maintain read performance.
How it works:
- Writes go to an in-memory sorted structure (memtable)
- When the memtable reaches a size threshold, it is flushed to disk as a sorted SSTable file
- Reads check the memtable first, then search SSTables from newest to oldest
- Background compaction merges SSTables to reduce the number of files and remove deleted/overwritten entries
Key property: Writes are sequential (appending to the memtable and flushing sorted files), which is much faster than the random I/O of B-Tree page splits. However, reads may need to check multiple SSTables, and compaction consumes CPU and I/O in the background.
Where LSM Trees are used: Apache Cassandra, RocksDB, LevelDB, HBase, ScyllaDB, InfluxDB. These are systems designed for high write throughput — time-series data, event logs, messaging systems.
Hash Index
A hash index maps keys to row locations using a hash function. It provides O(1) lookups for exact-match queries but cannot support range queries, sorting, or partial matches at all.
How it works: The index computes hash(key) to determine the bucket where the row pointer is stored. Lookups are constant time on average but degrade to O(n) in the worst case due to hash collisions.
Where Hash Indexes are used: PostgreSQL supports explicit hash indexes (though B-Trees are usually preferred). Memcached and Redis use hash tables as their primary data structure. MySQL Memory engine supports hash indexes. Hash indexes are most valuable for in-memory databases where the overhead of B-Tree traversal is proportionally significant.
Implementation