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.
- number of pages the table occupies
- number of rows
- average bytes per row, including the per-row overhead
- bytes per page, usually 8192
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.
| bytes per row | rows per page | pages, in hundreds of th | gigabytes | |
|---|---|---|---|---|
| a narrow row | 50.0 | 163.0 | 1.3 | 0.4 |
| a typical row | 200.0 | 40.0 | 5.0 | 1.6 |
| a wide row | 500.0 | 16.0 | 12.5 | 4.0 |
| a row with text in it | 1000.0 | 8.0 | 25.0 | 8.0 |
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.
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.
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