1CREATE INDEX books_id2ON books (id);3SELECT name FROM books4WHERE id = 28;WHERE id BETWEEN 17 AND 33;5INSERT INTO books6VALUES (30, 'Emma');[object Object][object Object][object Object][object Object]20401 level–2 levels–3 levels–(0,3)5(1,1)12(1,4)17(0,1)20(1,3)28(0,4)33(1,5)30(0,4)33(1,2)40(0,2)46(0,1)20(0,2)46(0,3)5(0,4)33(1,1)12(1,2)40(1,3)28(1,4)17(1,5)28(1,3)3030
Back: the SQL, the index's root page, and how far each level reachesMiddle: leaf pages; front: the table's pages of rowsMoving: search keys, a row address (TID), a range cursor and a separatorIn front: the books table, two pages of rows in the order they arrived. Behind them: a B-tree index on id, with sorted leaf pages and a root page whose separator keys steer each search. A search reads one page per level, then follows the row's TID to the table. A range walks right along the linked leaves. An insert into a full leaf splits it and copies a separator up. The toy pages hold at most 3 keys so the tree fits the picture. A real 8 kB PostgreSQL page holds hundreds of small keys, so real trees are wide and shallow.

Why an index

  1. Find the book with id 28. The table’s rows sit in its pages in the order they arrived, not sorted. With no index, the DBMS must read every page and test all 8 rows: a sequential scan.

Finding keys

Growing the tree

What happens

;; step 1/11 · scan
SELECT name FROM books WHERE id = 28;
no index: read every table page, test every row
  page 0: 20 46 5 33
  page 1: 12 40 28 17
8 rows tested, 1 matches ((1,3) Piranesi)
it cannot stop early: another row could have id 28 too
a million rows means a million tests
EXPLAIN calls this a Seq Scan

The toy pages hold at most 3 keys so the tree fits the picture. A real 8 kB PostgreSQL page holds hundreds of small keys, so real trees are wide and shallow. Strictly, this is a B+-tree: every key lives in a leaf, and the pages above only hold separator copies. PostgreSQL’s B-tree index (and most database “B-trees”) work this way.