Phase 7 · Performance: indexes and query plans · Topic 7.1

Indexes: how a B-tree makes lookups fast

Must know

An index is a separate, sorted copy of one or more columns — usually a B-tree — with pointers back to the rows, so the database can jump to matching rows instead of scanning the whole table.

Not started

Step 1 Understand it

Why you need it. Without an , WHERE order_date = '2024-02-12' must read every and check it: a . On 100 million rows that is slow, and it gets slower as the grows. An index turns it into a few quick steps.

New words in this topic (tap one to see what it means)

Watch it happen

AnimationAn index is a sorted copy you can search, instead of reading every row
Read the animation as text
  1. No index on order_date: the plan says SCAN orders. The database reads every row and checks its date.
  2. CREATE INDEX builds a separate, sorted copy of the dates, as a shallow tree. The leaves hold every date in order; the root holds signposts.
  3. SEARCH: start at the root. 02-12 is between 01-21 and 03-01, so take the middle branch.
  4. In that leaf, find 02-12 — it points straight at its row: order 106.
  5. Three steps instead of reading the whole table. For 100 million rows a B-tree is still only 3–4 levels deep. The cost: every INSERT, UPDATE and DELETE must update the index too.

Picture it

flowchart TD
  R["root page<br/>dates before 2024-02-01 · from 2024-02-01"] --> B1["branch<br/>Jan dates"]
  R --> B2["branch<br/>Feb and Mar dates"]
  B2 --> L1["leaf: 2024-02-02 → order 105"]
  B2 --> L2["leaf: 2024-02-12 → order 106"]
  B2 --> L3["leaf: 2024-02-20 → order 107"]
A index: sorted values in a shallow tree. Finding 2024-02-12 takes one step per level — root, branch, leaf — then one jump to the row, instead of reading every row.

The idea

A B-tree index keeps the 's values in sorted order, in a shallow tree: a root page points to branch pages, which point to leaf pages holding each value and the row it belongs to. To find a value, the walks from the root down — about 3 or 4 steps even for millions of rows — instead of reading everything. The is indexed automatically. Indexes speed up , … , and /MAX on the indexed column. The cost: every , and must also update every index, and indexes take space.

The shape of it

sql
CREATE INDEX index_name ON table_name (column);
CREATE UNIQUE INDEX index_name ON table_name (column);
DROP INDEX index_name;

Words in lower case are the parts you fill in.

How it works, step by step

  1. Without an index on order_date, the plan says SCAN orders: every row is read.
  2. CREATE INDEX idx_orders_date ON orders(order_date) builds a sorted list of dates, each pointing to its row.
  3. Now the plan says orders USING INDEX idx_orders_date (order_date=?): the database jumps to the date.
  4. A lookup by the primary key uses the table's own key: SEARCH … USING PRIMARY KEY.
  5. For 10 rows the difference is invisible; for 100 million it is the difference between a full read and a handful of page reads.

Analogy

The index at the back of a textbook. To find "photosynthesis" you don't read every page; you look it up alphabetically and jump to page 214. A full table scan is reading the book cover to cover.

Where the analogy breaks

A book index never changes once printed. A database index must be updated on every write — so a table with ten indexes is slow to write to. Index what you search by, not everything.

Worked examples

No index: the database scans every row

sql
EXPLAIN QUERY PLAN
SELECT * FROM orders WHERE order_date = '2024-02-12';

1 row

idparentnotuseddetail
20216SCAN orders

shows the plan instead of running the . Read the detail column: SCAN means every row.

With an index: the database searches

sql
CREATE INDEX idx_orders_date ON orders(order_date);
EXPLAIN QUERY PLAN
SELECT * FROM orders WHERE order_date = '2024-02-12';

1 row

idparentnotuseddetail
3062SEARCH orders USING INDEX idx_orders_date (order_date=?)

SEARCH … USING INDEX means it jumps straight to the matching date.

The primary key is already indexed

sql
EXPLAIN QUERY PLAN
SELECT * FROM orders WHERE order_id = 105;

1 row

idparentnotuseddetail
2033SEARCH orders USING INTEGER PRIMARY KEY (rowid=?)

Must-know points

  • An index is a sorted structure (usually a B-tree) pointing back to the rows.
  • It helps WHERE, JOIN … ON, ORDER BY and on the indexed columns.
  • Primary keys and get an index automatically; usually don't — index them yourself.
  • Every index slows down writes and uses space. Index the columns you filter and join on.
  • An index helps most when it narrows the search to a small share of the rows (high ).
  • B-trees versus LSM trees, and indexing at scale, are in the System Design tab, Phase 3.

Common mistakes

  • Indexing every column "just in case", which slows every write.
  • Forgetting to index foreign keys, so joins and deletes on the parent table are slow.
  • Indexing a column with only a few values (like status) and expecting it to help a query that matches half the table.

In other databases

  • also has hash, GIN (for arrays, JSON and full-text) and BRIN (for huge, naturally ordered tables) indexes. InnoDB stores the table itself in primary-key order (a ).

Step 2 Go further (free, optional)

Optional. The notes above are written to be enough on their own. These free resources give a second explanation and more practice.

Step 3 Practise

Try each one yourself first: out loud, or written and run in the practice box below, which saves every query you run. Then open the answer and compare.

  1. ExplainSay what it does and when to use it, in plain words, with no notes.

    Explain an index using the textbook analogy, and say what it costs.

    Show answer

    An index is like the index at the back of a textbook. To find "photosynthesis" you don't read every page; you look the word up in the sorted list and jump to page 214. Without an index, the database has to read the whole table (a full scan), like reading the book cover to cover. With one, it searches a sorted copy of the column (a B-tree) and follows a pointer straight to the matching rows.

    What it costs:
    • Slower writes: every INSERT, DELETE, and UPDATE of the indexed column must also update the index. A book's index is printed once; a database's is kept up to date all the time.
    • Space: the index is a second copy of the column, on disk and in memory.
    So you index the columns your important queries filter, join or sort on, not every column.

  2. Writefrom a blank page, then run it

    Create an index that would help SELECT * FROM employees WHERE dept_id = 1;, and check the plan before and after.

    Show answer

    Before, the plan is SCAN employees: every row is read. Create an index on the filtered column, run the same EXPLAIN QUERY PLAN, and it becomes a SEARCH using the new index.

    (With 8 rows a scan is just as fast; the plan matters when the table has millions.)

    sql
    CREATE INDEX idx_employees_dept ON employees(dept_id);
    EXPLAIN QUERY PLAN SELECT * FROM employees WHERE dept_id = 1;

    1 row

    idparentnotuseddetail
    3061SEARCH employees USING INDEX idx_employees_dept (dept_id=?)
  3. Predictsay it before you run it

    Will SELECT * FROM customers WHERE customer_id = 3 use an index without you creating one? Why?

    Show answer

    Yes. customer_id is the primary key, and a primary key is always indexed: the database needs that to check uniqueness on every insert. In an INTEGER PRIMARY KEY is the row's own id (the rowid), so the plan searches straight to it.

    sql
    EXPLAIN QUERY PLAN SELECT * FROM customers WHERE customer_id = 3;

    1 row

    idparentnotuseddetail
    2033SEARCH customers USING INTEGER PRIMARY KEY (rowid=?)
  4. Edge casesSay what happens with NULLs, ties, duplicates or no rows at all, and fix it.

    A status column has only four values and 50% of rows are 'delivered'. Would an index on status help WHERE status = 'delivered'? What about WHERE status = 'pending'?

    Show answer

    WHERE status = 'delivered': probably not. Half the table matches, so the index would find half a million row pointers (in a million-row table), and fetching each row one by one, jumping around the disk, is slower than simply reading the whole table in order. A good , with , ignores the index and scans.

    WHERE status = 'pending': yes, if pending orders are rare (say 1%). The index jumps straight to the few matching rows. This is the case where an index on a column with few values can pay off.

    The usual answer for "a rare value of a common column" is a partial index, which only holds the rows you search for: CREATE INDEX idx_orders_pending ON orders(order_date) WHERE status = 'pending';. It is tiny and cheap to maintain.

    (SQLite only knows how values are spread after you run ANALYZE. Without it, it will happily use an index on status for 'delivered' too.)

Practice box: write and run your answers here

Ctrl/⌘ + Enter runs it. Select part of the query to run only that part.

Step 4 Interview-ready, then prove it

Your interview answer

About 20 to 40 seconds, said out loud.

An index is a separate sorted structure, typically a B-tree, mapping column values to row locations, so the database can find matching rows in logarithmic time instead of scanning the table. I index columns used in selective WHERE filters, join keys — especially foreign keys — and ORDER BY, and I keep in mind the trade-off: every index adds work to inserts, updates and deletes, and uses storage.

Follow-up questions

What is the difference between a clustered and a non-clustered index?

A clustered index stores the table's rows themselves in index order (one per table, often the primary key, as in and MySQL InnoDB). A non-clustered index is a separate structure that points to the rows.

Why not index every column?

Each index must be updated on every write and takes space, and the optimiser can only use a few per query anyway.

Why might the database ignore your index?

If the query matches a large share of the rows, a full scan is cheaper than thousands of index jumps. Or the query wraps the column in a function, so the index can't be used.

Tick each check when you can do it without notes

A topic is finished when you pass all four checks, not when you have read it. Be strict: if you had to look at the notes, it is not ticked yet.

  • Explain Say what it does and when to use it, in plain words, with no notes.
  • Write Write the query from a blank page, then run it to check.
  • Predict Read a query and say which rows it returns before you run it.
  • Edge cases Say what happens with NULLs, ties, duplicates or no rows at all, and fix it.

0 of 4 checks ticked.

Saved in this browser. Back it up or move it to another device.