Skip to content

Introduction

The previous chapter ended with an uncomfortable number: finding one record in a 3 000-block heap file costs about 1 500 block accesses, and even a sorted file only gets that down to 12 — and only when the search is on the ordering field.

Indexes are additional access structures built on top of a file to speed up retrieval on fields that the file's own organization does not help with.

Definition

An index is an auxiliary file whose entries are pairs

field value, pointer

sorted on the field value, so that a search on that field can be answered by searching the (much smaller) index instead of the data file.

The analogy is the index at the back of a book: it is much thinner than the book, it is sorted, and each entry tells you which page to turn to. Two properties make it work, and both matter for database indexes too:

  • the index is smaller than the data — an entry holds one field value and a pointer, not a whole record, so far more entries fit in a block;
  • the index is ordered, so binary search applies even when the data file is unordered.

An index is defined on a single field of the file, called the indexing field (or indexing attribute). A file can have several indexes, on different fields, all at the same time.

Dense vs. sparse

Every index in this note is one of two kinds, and the distinction decides its size:

Definition

A dense index has an index entry for every record (every search key value) in the data file.

A sparse index has entries for only some of the values — typically one per block of the data file.

A sparse index is only possible when the data file is physically ordered on the indexing field: if the file is sorted, an entry pointing at a block is enough, because everything between two consecutive index entries must live in the blocks between their pointers. On an unordered field, the records with a given value are scattered, so nothing but a dense index will do.

The running example

All the cost numbers in this note come from one file, so the index types can be compared directly.

ParameterValue
Records r30 000
Block size B1 024 bytes
Record size R100 bytes, fixed length, unspanned
Blocking factor bfr=B/R10 records/block
File size b=r/bfr3 000 blocks

The file is ordered on the key field Ssn (9 bytes). A block pointer P is 6 bytes, a record pointer Pr is 7 bytes.

Without any index: linear search averages b/2=1500 block accesses, binary search on Ssn costs log23000=12.

Primary Index

Definition

A primary index is an ordered file whose records are of fixed length with two fields: the value of the ordering key field of the data file, and a pointer to a disk block. There is one index entry per block of the data file.

The value stored in entry i is the ordering key value of the first record in block i — that record is called the block anchor.

Primary index on the ordering key field Ssn: each index entry holds the Ssn of the first record (block anchor) of a data block together with a pointer to that block

A primary index is therefore:

  • sparse — one entry per block, not per record;
  • available on at most one field per file, since a file can be physically ordered only one way;
  • dependent on the ordering field being a key (unique values).

Why it is small

Ri=V+P=9+6=15 bytesbfri=102415=68 entries/block

The number of entries ri equals the number of data blocks, 3 000, so

bi=300068=45 blocks

A binary search on the index costs log245=6 accesses, plus one access to fetch the data block:

6+1=7 block accesses, versus 12 for binary search on the data file

Reading the entry that "isn't there"

Searching a primary index does not look for an exact match. To find record K, the search locates the last index entry whose value is K — entry i with KiK<Ki+1 — and follows its pointer. The record, if it exists, is in that one block.

The insertion problem

A primary index inherits the sorted file's weakness and adds one of its own: inserting a record both shifts records in the data file and may change a block anchor, forcing index entries to change too.

The standard remedies are the ones from sorted files: an overflow file of unsorted new records merged in periodically, or a linked list of overflow records hanging off each block. Deletion again uses deletion markers, so the index only needs updating when a block anchor disappears.

Clustering Index

Definition

A clustering index is used when the data file is physically ordered on a non-key field — a field with duplicate values, called the clustering field. It has one entry per distinct value of that field, pointing to the first block that contains a record with that value.

Clustering index on the non-key ordering field Dept_number: one index entry per distinct department value, each pointing to the first data block containing records of that department

It is also sparse — entries exist per distinct value, not per record — and, like the primary index, a file can have at most one, because it depends on physical ordering.

Suppose the 30 000-record file is instead ordered on Dept_number (4 bytes) with 1 000 distinct departments:

Ri=4+6=10,bfri=102410=102,bi=1000102=10 blockssearch cost=log210+1=4+1=5 block accesses

That locates the first block of the matching department; the remaining records of that department follow in the next blocks and are read sequentially.

Blocks reserved per value

Insertion is still expensive, because records must stay grouped by value. A common fix is to give each distinct value its own block (or chain of blocks), linked by pointers. Insertion then only touches the chain for that value, and the index entry never has to move — at the cost of wasting space in partially filled blocks.

Secondary Index

Definition

A secondary index is an ordered file on a field that is not the physical ordering field of the data file. The indexing field is called a secondary key.

This is the index type that a file can have many of — one for every field that queries filter on. Because the data file is not ordered on the field, the index cannot be sparse.

Case 1 — the field is a key

One entry per record, pointing to the record (or to its block): a dense index, sorted on the secondary key value.

Secondary index on a non-ordering key field: a dense index file with one entry per record of the unordered data file, each entry pointing directly at its record

For the running example, a secondary index on a 9-byte key field:

Ri=9+6=15,bfri=68,ri=r=30000,bi=3000068=442 blockssearch cost=log2442+1=9+1=10 block accesses

Compare that with b/2=1500 for a linear scan of the data file — a factor of 150 improvement, bought with 442 blocks of extra storage.

A secondary index is much bigger than a primary one

442 blocks against 45, for the same file and the same field width. The difference is entirely density: one entry per record instead of one per block. This is why a single-level secondary index is rarely enough, and why multilevel indexes matter most here.

Case 2 — the field is not a key

Now several records share a value, and the index has to point to all of them. Three options:

OptionHow it worksCost
Duplicate entriesOne index entry per record, with the value repeatedIndex no longer has unique keys; largest of the three
Variable-length entriesOne entry per distinct value, holding a list of record pointersVariable-length records in the index — awkward to search and update
Level of indirection (usual choice)One entry per distinct value, pointing to a block of record pointers; that block lists every record with the valueIndex stays fixed-length and dense on distinct values; costs one extra block access

Secondary index with a level of indirection: each index entry for a distinct field value points to a block of record pointers, which in turn point at all records holding that value

The indirection option also makes retrieval of all matching records convenient: read one pointer block, then fetch exactly the records needed — no scanning.

Comparison

Ordering field?Key field?Dense/SparseNumber of entriesPer file
Primary indexYesYesSparseOne per blockAt most 1
Clustering indexYesNoSparseOne per distinct valueAt most 1
Secondary index (key)NoYesDenseOne per recordMany
Secondary index (non-key)NoNoDense on valuesOne per distinct value (with indirection)Many

Cost for the running example, all on the same 3 000-block file:

Access pathBlock accesses
Linear search, no index1 500
Binary search on ordering field12
Primary index (45 blocks)7
Clustering index (10 blocks)5
Secondary index on a key (442 blocks)10

What is still wrong

Every number above comes from a binary search on the index file, which is itself just an ordered file — so the index has exactly the problems the data file had: log2bi accesses, and expensive insertion.

The obvious move is to apply the idea again and index the index. That is the multilevel index, and it is the subject of the next note.

Built with ❤️ and curiosity