ContentsThe library

How an Index Actually Works

The Database Never Reads a Row

A scan costs what it costs because of pages, not rows. Work out the number of pages and the answer falls out, along with the case where scanning is correct.

Before anything can be said about indexes, there has to be a baseline, and the

baseline is reading the whole table. It is worth doing carefully, because

almost every surprise later in the course is a case where the baseline was

cheaper than people assumed.

Rows live in pages

A database does not read rows. It reads pages: fixed-size blocks, eight

kilobytes in most systems, which is the smallest thing storage will hand over.

A row lives inside a page along with as many of its neighbours as fit, and

reading one row means reading the whole page it sits on.

This single fact decides the cost of nearly everything.

FIG 1How many pages a table occupies
number of pages the table occupies
number of rows
average bytes per row, including the per-row overhead
bytes per page, usually 8192
Ten million rows of two hundred bytes each is two gigabytes, which is about 244,000 pages. Note what is absent: the number of columns you selected. The page arrived whole, so asking for one column of a wide row saves nothing on the read, which is the first thing that surprises people.

The practical consequence is that row width is a performance decision. Two

hundred bytes a row gives forty rows per page; a thousand bytes a row gives

eight. The same query over the same ten million rows reads five times as many

pages in the second case, and nothing about the query changed.

FIG 2The same ten million rows, at four row widths
bytes per rowrows per pagepages, in hundreds of thgigabytes
a narrow row50.0163.01.30.4
a typical row200.040.05.01.6
a wide row500.016.012.54.0
a row with text in it1000.08.025.08.0
The two marked cells are a factor of five in every cost that follows, bought or lost at the point the table was designed. The widest row here is what happens when a description column lives alongside the identifiers everybody actually filters on, and splitting that column into its own table is the oldest performance fix in the subject.

The cost of a scan

Now the time. A scan reads every page of the table in storage order, which

means one long sequential request rather than a quarter of a million small

ones. On the sort of storage a database runs on today, that runs at something

like one to two gigabytes a second.

Two gigabytes at 1.6 gigabytes a second is about 1.3 seconds of reading. Add

the processor work of walking the rows in each page and applying the filter,

and a scan of ten million typical rows takes a couple of seconds.

FIG 3Where the two and a half seconds of a scan actually go
Roughly half of a scan is not storage at all. That matters twice over: once because a table already sitting in memory still costs the other half, and once because the filter, which is the part people think of as the work, is the smallest slice on the chart.

The important property of this number is what it does not depend on. It does

not depend on how many rows match. One matching row or ten million, the same

pages were read and the same bytes were decoded. A scan has a cost and not a

range.

Sequential against scattered

An index reads differently. It finds the identifiers of the matching rows and

then fetches each one, and those rows are wherever they happen to be, which

means one small request per page rather than one long one.

Each of those requests carries its own latency. On a spinning disk the physical

arm movement made a scattered page roughly a hundred times the cost of a

sequential one, and every rule of thumb in the subject was built on that

ratio. On solid-state storage there is no arm, and the gap has narrowed to

something like four or five to one.

That change matters more than it sounds. Rules of thumb inherited from the

1990s assume random access is catastrophic; on modern hardware it is merely

expensive. Databases encode the current ratio as a pair of planner constants

you can read and, if your storage really is unusual, adjust.

When a scan is right

Put the two costs on the same axes and the crossover appears.

FIG 4Time to answer, against the fraction of the table that matches
0.002.505.007.5010.000.025.050.075.0100.0per cent of rows matching the filter
scan the tableindex, then fetch each rowindex that answers on its own
The flat line is the scan: the same cost whatever matches. The steep line is the ordinary index path, and it crosses the scan at about thirty per cent, above which the index is the slower plan. The shallow line is an index that contains everything the query asked for and never touches the table, which wins everywhere, and is the subject of the sixth lesson.

Thirty per cent is the number to carry around, with the understanding that it

is a region rather than a threshold: it falls as rows get wider and rises as

storage gets faster. The reasoning behind it is what matters. If a filter

matches a third of a table whose rows are scattered uniformly, then nearly

every page of the table holds at least one matching row, so the index path ends

up reading nearly every page anyway, one at a time, after having read the index

first.

This explains a result that otherwise looks like a bug: an index on a column

with three possible values is almost never used, because any filter on it

matches a third of the table. It is not that the database failed to notice the

index. It considered it, costed it, and correctly concluded that scanning was

faster.

The next lesson takes the other road. If scanning is what it costs because

nothing about the layout helps, what is the cheapest change to the layout that

does help? The answer is to sort the file, and the trouble that creates is the

rest of the course.

Recap

  • The unit of work is the page, not the row, so the cost of reading a table is set by how many rows fit on a page and almost nothing else.
  • A scan reads sequentially, which on any storage is several times cheaper per byte than reading the same bytes in scattered pieces, and that ratio is why an index can lose.
  • Above roughly a quarter to a third of the table, scanning is the right answer and an index is the slower plan, which is the one case worth memorising.

This is the reading half

Starting the course gives you your own copy of it. Every idea on every page has problems standing under it, marked with a reason rather than a tick, and any sentence you do not believe can be opened and argued with. None of that can happen on a page nobody owns.

The contents

NextSorted Means Searchable →

The rest of this course

  1. 01The Database Never Reads a Rowyou are here
  2. 02Eighteen Reads Instead of a Quarter of a Millionopening only
  3. 03Three Reads, and Two of Them Were Already in Memoryopening only
  4. 04The Page Is Full, So Cut It in Halfopening only
  5. 05A Phone Book Sorted by Surname Then First Nameopening only
  6. 06The Index Already Knew the Answeropening only
  7. 07One Is Faster and Cannot Do Ranges, the Other Is for Writingopening only
  8. 08The Index Is Right There and the Database Will Not Use Itopening only

Read alongside