Ecommet
Topic 27 of 75

Database Indexes

An index lets the database find rows without reading the whole table, turning seconds into milliseconds. Learn how B-tree and LSM-tree indexes work, composite indexes, and when an index makes things worse.

Intermediate 6 min read 4 quiz questions

Jump to the quiz

In a nutshell

  • Without an index, a query reads every row (a full scan). An index is a sorted structure that jumps straight to matches.
  • Most relational databases use B-trees: balanced, shallow, good for lookups and ranges. Write-heavy stores often use LSM trees.
  • A composite index on (a, b) helps queries filtering on a, or on a and b, but not on b alone.
  • Every index speeds up reads and slows down writes and uses storage. Index for your real queries, not every column.

Why indexes matter

Finding one customer by email in a table of 10 million rows without an index means checking all 10 million. With a B-tree index on email, the database walks a tree three or four levels deep and lands on the row. It is the same reason a book has an index at the back.

  • 10,000,000rows checked by a full scan
  • ~4tree levels walked with a B-tree index (with hundreds of keys per page)

Create an index, then check the plan uses it

SQL
CREATE INDEX idx_users_email ON users (email);

EXPLAIN SELECT id, name FROM users WHERE email = 'priya@example.com';
-- Look for an index scan on idx_users_email, not a sequential (full) scan.

B-trees

A B-tree keeps keys sorted in fixed-size pages (often 8 or 16 KB). Each inner page holds hundreds of keys pointing to child pages, so the tree is very wide and very shallow. A lookup reads one page per level; a range query (WHERE created_at BETWEEN …) finds the start and then walks along the leaf pages in order.

Looking up email "priya@…" in a B-tree

Looking up email "priya@…" in a B-tree Root page → Inner page Root page → Inner page: p ≤ key < t Inner page → Leaf page Leaf page → Table row: fetch Root pagea… | h… | p… | t…Inner pageh… to o…Inner pagep… to s…Leaf pagepriya@ → row 8812Table rowid 8812p ≤ key < tfetch
Each level narrows the search by comparing against sorted keys. Leaves point to the rows.
Diagram as text
  • Root page → Inner page
  • Root page → Inner page: p ≤ key < t
  • Inner page → Leaf page
  • Leaf page → Table row: fetch

LSM trees

Databases built for heavy writes (Cassandra, RocksDB, and the storage engines inside many others) often use a log-structured merge tree. Writes go to an in-memory sorted table and an append-only log; when the memory table fills, it is flushed to disk as an immutable sorted file. Background compaction merges files and drops old versions.

B-treeLSM tree
WritesUpdate pages in place (random I/O)Append and flush sequentially: very fast
ReadsOne path down the tree: predictableMay check several files; Bloom filters help
Background workLittleCompaction uses disk and CPU
Typical useGeneral-purpose relational databasesWrite-heavy stores, time series, logs

Composite and covering indexes

A composite index sorts by several columns, like a phone book sorted by last name, then first name. It helps queries that use its columns from the left.

One composite index, three queries

SQL
CREATE INDEX idx_orders_customer_date ON orders (customer_id, created_at);

-- Uses the index: filters on the first column
SELECT * FROM orders WHERE customer_id = 7;

-- Uses the index fully: first column equal, second column range, already sorted
SELECT * FROM orders WHERE customer_id = 7 AND created_at > '2026-01-01' ORDER BY created_at;

-- Cannot seek with it: skips the first column
SELECT * FROM orders WHERE created_at > '2026-01-01';

A covering index contains every column the query needs, so the database answers from the index alone without visiting the table. Some databases let you add extra columns for this (INCLUDE in PostgreSQL and SQL Server).

When an index hurts

  • Writes get slower: every insert, update and delete must also update every index on the table.
  • Storage grows: indexes can be as large as the table.
  • Low selectivity: an index on a column with two values (is_active) rarely helps; reading half the table through an index is slower than a scan.
  • Functions hide the column: WHERE LOWER(email) = … cannot use a plain index on email (use an expression index, or store it normalised).
  • Leading wildcards: LIKE '%gmail.com' cannot use a B-tree; consider a search engine or a reversed column.

Common mistakes

  • Adding an index on every column "just in case", slowing every write.
  • Building a composite index in the wrong column order for the queries that use it.
  • Wrapping indexed columns in functions or casts in WHERE clauses.
  • Guessing instead of reading the query plan.

Interview questions

Try answering out loud before you open each one.

A query got slow as the table grew. How do you investigate?

Run EXPLAIN ANALYZE to see whether it scans the full table and where time goes. Check which columns it filters, joins and sorts on, and add an index matching them (composite, in the right order, possibly covering). Re-check the plan. Also check for functions on columns and for returning far more rows than needed.

B-tree vs LSM tree?

B-trees update pages in place: predictable reads and good range scans, the default for relational databases. LSM trees append writes and merge files in the background: much higher write throughput, reads may touch several files (helped by Bloom filters), and compaction costs I/O. Choose LSM for write-heavy workloads.

Check yourself

1. Index on (customer_id, created_at). Which query cannot seek using it?
Show the answer

Answer: WHERE created_at > X. A composite index is used from its leftmost column; skipping customer_id leaves nothing to seek on.

2. What is the main cost of an index?
Show the answer

Answer: Slower writes and more storage. Every write must update every index too.

3. Which structure suits a very write-heavy workload?
Show the answer

Answer: LSM tree. LSM trees turn writes into sequential appends and merge later.

4. Why might WHERE LOWER(email) = ? ignore an index on email?
Show the answer

Answer: The index stores email, not LOWER(email). Index the expression itself, or store emails already in lower case.

Further reading

Your notes

Saved in this browser as you type.

Finished this topic?

Mark it read to track your progress, then carry on with the next one.

Next topic