Introduction

An index is an auxiliary file that speeds up search on a field, the indexing field or search key. Each index entry is a pair ⟨K,P⟩\langle K, P \rangle: a key value and a pointer to a data block or record. Index entries are much smaller than data records, so many fit in a block, and a search through the index reads far fewer blocks than a search through the data file.

The calculations here start from the block size, the record and field sizes and the number of records, and find the number of index entries, index blocks, levels of a multilevel index, or block accesses for a search. The single-level and multilevel calculations here lead directly to B-Trees and B+ Trees.

Prerequisites

Core concepts

Index blocking factor. With key size VV and pointer size PP, an entry has V+PV + P bytes and bfri=⌊B/(V+P)⌋\text{bfr}_i = \lfloor B / (V + P) \rfloor entries fit in a block of BB bytes.

Dense and sparse.

  • Dense index: distinguish value-density (an entry for every distinct search-key value) from record-density (an entry for every record). State which convention is used.
  • Sparse (nondense) index: entries for only some values, typically one per data block. It works only on a file sorted on the indexing field: find the largest entry ≤\le the key and scan that block.

Three single-level ordered indexes (Elmasri and Navathe's names):

  • Primary index: on the ordering key field of a sorted file. One entry per data block, holding the key of the block's first record (the block anchor). Sparse; entries = number of data blocks.
  • Clustering index: on an ordering non-key field. One entry per distinct value, pointing to the first block containing it. Under the Elmasri record-density convention this is nondense (sparse), although it covers every distinct value.
  • Secondary index: on any non-ordering field. Dense: one entry per record for a key field; for a non-key field, one entry per distinct value pointing to a block of record pointers (an extra level of indirection), or one entry per record.

A file has at most one physical order, so it has at most one primary or clustering index (not both), but any number of secondary indexes.

Terminology warning. Korth, Silberschatz and Sudarshan call any index on the field that orders the file a clustering (or primary) index and every other index nonclustering (or secondary). In Elmasri and Navathe "primary" means ordering key and "clustering" means ordering non-key. The Elmasri and Navathe meanings are the ones used in the definitions and examples; when a question uses one of these words, check from its wording whether the field is a key and whether it orders the file.

A sorted data file of four blocks with keys 5, 10, 15; 20, 25, 30; 35, 40, 45; 50, 55, 60; a sparse index with the four block anchors 5, 20, 35, 50 and a dense index with all twelve keys
Figure 1. A sparse primary index stores one block anchor per data block; a dense index stores every key.

How it works: search costs

  • Single-level index: with a nonempty uncached index, ordinary successful binary search of bi≥1b_i\ge1 blocks has worst-case ⌊lg⁡bi⌋+1\lfloor\lg b_i\rfloor+1 index-block probes, followed by a data access: ⌊lg⁡bi⌋+2\lfloor\lg b_i\rfloor+2 accesses. In particular, one index block still requires one read. Cached blocks and empty-index queries require separate accounting.
  • Multilevel index: let r1≥1r_1\ge1 be the number of first-level entries and integer fan-out fo≥2\mathrm{fo}\ge2. Define b1=⌈r1/fo⌉b_1=\lceil r_1/\mathrm{fo}\rceil, then bj=⌈bj−1/fo⌉b_j=\lceil b_{j-1}/\mathrm{fo}\rceil. Stop at the first t≥1t\ge1 with bt=1b_t=1. Thus t=max⁡(1,⌈log⁡for1⌉)t=\max(1,\lceil\log_{\mathrm{fo}} r_1\rceil). An uncached search reads one block per level plus one data block, t+1t+1 accesses; even r1=1r_1=1 needs one index level.
  • Secondary index on a non-key field: after the index search, one more access for the block of record pointers, then one access per matching record in the worst case.
A three-level index: 442 first-level blocks, 7 second-level blocks and one top block, fan-out 68, with a search path reading one block per level and then one data block
Figure 2. A multilevel index on 30000 entries with fan-out 68: levels of 442, 7 and 1 blocks; a search reads 4 blocks.

Mathematical explanation

Why levels are logarithmic in the fan-out. Each level reduces the number of blocks by a factor of fo\text{fo}, since one entry of level j+1j + 1 points to one block of level jj. After tt levels r1/fot≤1r_1 / \text{fo}^t \le 1, with at least one nonempty index level, so t=max⁡(1,⌈log⁡for1⌉)t = \max(1,\lceil \log_{\text{fo}} r_1 \rceil). For r1=1r_1=1, this gives one index level and two uncached reads including the data block. For ordinary successful binary search of bi≥1b_i\ge1 index blocks, the worst-case index probes satisfy W(0)=0W(0)=0 and W(bi)=1+W(⌊bi/2⌋)W(b_i)=1+W(\lfloor b_i/2\rfloor), since the larger remaining half has ⌊bi/2⌋\lfloor b_i/2\rfloor blocks. Thus W(bi)=⌊lg⁡bi⌋+1W(b_i)=\lfloor\lg b_i\rfloor+1, followed by one data-block read: ⌊lg⁡bi⌋+2\lfloor\lg b_i\rfloor+2 uncached reads in total. The shortcut ⌈lg⁡bi⌉\lceil\lg b_i\rceil agrees with the index-probe count only when bib_i is not a power of two; at a power of two, including 1, it undercounts by one.

Why a sparse index needs a sorted file. The entry for block jj says only that keys in block jj lie between its anchor and the next anchor. That is true only if the file is sorted on the indexing field.

Counting entries.

IndexEntriesDense or sparse
Primarynumber of data blocks bbsparse
Clusteringnumber of distinct values of the fieldnondense under the record-density convention; covers every distinct value
Secondary on a key fieldnumber of records rrdense
Secondary on a non-key field (indirection)number of distinct valuesdense in values

Worked examples

The data file is the one from File Organization: r=30000r = 30000 records of R=100R = 100 bytes, B=1024B = 1024, unspanned, so bfr=10\text{bfr} = 10 and b=3000b = 3000 data blocks. Key size V=9V = 9 bytes, pointer size P=6P = 6 bytes, so each entry is 15 bytes and bfri=⌊1024/15⌋=68\text{bfr}_i = \lfloor 1024 / 15 \rfloor = 68.

Example 1: primary index. The file is sorted on the key. Entries: 30003000 (one per block). Index blocks: ⌈3000/68⌉=45\lceil 3000 / 68 \rceil = 45. Search: ⌈lg⁡45⌉+1=7\lceil \lg 45 \rceil + 1 = 7 accesses, against ⌈lg⁡3000⌉=12\lceil \lg 3000 \rceil = 12 with binary search on the data file.

Example 2: secondary index on a key field. The file is not sorted on this field. Entries: 3000030000. Index blocks: ⌈30000/68⌉=442\lceil 30000 / 68 \rceil = 442. Search: ⌈lg⁡442⌉+1=10\lceil \lg 442 \rceil + 1 = 10 accesses, against about 15001500 for a uniformly located existing key (exactly 1500.51500.5 for equally occupied blocks) for a linear scan. For both 45 and 442 the rounding conventions ⌈lg⁡b⌉\lceil \lg b \rceil and ⌊lg⁡b⌋+1\lfloor \lg b \rfloor + 1 agree.

Example 3: multilevel version of Example 2 (Figure 2). Level 1: 442 blocks. Level 2: ⌈442/68⌉=7\lceil 442 / 68 \rceil = 7 blocks. Level 3: 1 block. So t=3=⌈log⁡6830000⌉t = 3 = \lceil \log_{68} 30000 \rceil, and a search reads 3+1=43 + 1 = 4 blocks.

Example 4: multilevel version of Example 1. Level 1: 45 blocks; level 2: 1 block. t=2t = 2, search cost 33.

Example 5: clustering index. The file is sorted on a non-key field with 20 distinct values. Entries: 20, all in one index block. A search reads the index block, then the first block containing the value, then the following blocks while the value continues.

Example 6: dense against sparse (Figure 1). Four data blocks with keys 5 to 60 in steps of 5, three per block. The sparse index has the four anchors 5, 20, 35, 50; the dense index has all 12 keys. To find 40 with the sparse index, take the largest anchor ≤40\le 40, namely 35, and scan that block.

Example 7: clustering index with numbers. Same file, sorted on a non-key field with 20 distinct values, each held by 30000/20=150030000 / 20 = 1500 records. The 20 entries fit in one index block.

  • If each value starts in a new block (the layout Elmasri and Navathe draw for a clustering index), one value's records fill exactly 1500/10=1501500 / 10 = 150 blocks. Search cost: 1+150=1511 + 150 = 151 accesses for all records of one value.
  • If the file is packed with no gaps, every run has 1500 records, a multiple of the 10-record block capacity, so every value also starts at a block boundary. Every run spans exactly 150 data blocks: 1+150=1511+150=151 accesses. A 151-block run would require a nonzero starting slot, which these uniform runs do not have.

The two layouts give the same answer in this uniform, exactly divisible example; layout and starting offset matter for other run sizes.

Example 8: secondary index on a non-key field, with indirection. Same file, a non-ordering field with 1000 distinct values, 30 records each. One entry per value: 1000 entries, ⌈1000/68⌉=15\lceil 1000 / 68 \rceil = 15 index blocks. Each entry points to a block of 30 record pointers (30×6=18030 \times 6 = 180 bytes, one block).

  • Single level: ⌈lg⁡15⌉=4\lceil \lg 15 \rceil = 4 index accesses, 11 for the pointer block, and up to 3030 data blocks, since the 30 records can lie in 30 different blocks: 4+1+30=354 + 1 + 30 = 35 in the worst case.
  • Multilevel: level 2 has ⌈15/68⌉=1\lceil 15 / 68 \rceil = 1 block, so t=2t = 2 and the cost is 2+1+30=332 + 1 + 30 = 33.

The data-block term dominates: an index on a field with many records per value saves little over a scan when those records are scattered.

Important properties and observations

  • Entry size is key plus pointer; bfri=⌊B/(V+P)⌋\text{bfr}_i = \lfloor B/(V+P) \rfloor is the fan-out of a multilevel index.
  • Primary and clustering indexes are sparse and need the file sorted on the field; secondary indexes are dense.
  • At most one primary or clustering index per file; many secondary indexes.
  • Multilevel search cost: for a nonempty uncached index with r1≥1r_1\ge1 first-level entries and integer fan-out fo≥2\text{fo}\ge2, t+1t+1 reads with t=max⁡(1,⌈log⁡for1⌉)t=\max(1,\lceil\log_{\text{fo}}r_1\rceil). In particular, r1=1r_1=1 needs one index level and two total reads including the data block.
  • A secondary index can give a particularly large gain. Selective lookups on an otherwise non-ordering field avoid a full scan, but the total cost includes all matching data blocks.
  • Range queries: a primary or clustering index reads consecutive data blocks; a secondary index may need one data block per matching record.

Common misconceptions.

  • "A primary index has one entry per record." One per data block.
  • "A dense index is always larger than the data file." Entries are small; Example 2 has 442 index blocks against 3000 data blocks.
  • "The term clustering index means the same in every book." It does not; see the terminology warning.

GATE examination perspective

  • Entries and blocks. Consider a primary or secondary index from BB, VV, PP, rr, RR.
  • Levels of a multilevel index. Also consider block accesses t+1t + 1.
  • Dense or sparse. Also consider which index types a file can have.
  • Comparisons. Consider search cost with and without an index.
  • Rounding. Floors for blocking factors, ceilings for block counts and levels.

Summary

Sources

  1. A. Silberschatz, H. F. Korth, S. Sudarshan, Database System Concepts, 7th ed., McGraw-Hill, 2019 (2020 copyright), 14.1 Basic Concepts, 14.2 Ordered Indices.
  2. R. Elmasri, S. B. Navathe, Fundamentals of Database Systems, 7th ed., Pearson, 17.1 Types of Single-Level Ordered Indexes, 17.2 Multilevel Indexes.

Next steps