skip to content

Some engines store a physical row address (page and slot) in each secondary-index entry, while others store the table's primary-key value instead. Compare the two designs and their consequences for reads and for updates.

level: middleimportance: must knowfreq 48%

answer

  1. physical = where it is; logical = what it is
  2. heap: one page read, but row moves
  3. forwarding pointer = double hop
  4. clustered key: stable, but second descent
  5. changing the primary key rewrites every secondary index

basics

~20 s

A physical address makes each lookup a single direct page read, but any row move must be repaired - either by updating every secondary index or by leaving a forwarding pointer. A primary-key locator survives row movement untouched, but every lookup costs a full B+Tree descent and entries are larger.

solid answer

~60 s

**Physical locator (heap tables).** The entry holds file, page, and slot. A lookup is one page read - the cheapest possible. The problem is stability: if an update makes a row too large for its page and it must move, every secondary index pointing at it would need updating. Engines avoid that fan-out cost by leaving a forwarding stub at the old location, so lookups then cost two page reads until a reorganization cleans it up. **Primary-key locator (clustered tables).** The entry holds the primary-key value, so it is a logical reference. Rows can move freely within the clustered tree - page splits, reorganization - with no secondary-index maintenance at all. The price is paid on every read: resolving the locator means descending the clustered B+Tree, several page reads instead of one. Entries are also wider because they carry the whole key, which lowers fanout, and updating a primary key means rewriting the corresponding entry in every secondary index. So it is a classic trade: cheap reads with maintenance risk, versus stable maintenance with a per-read tax.

go deeper

for a junior

Know that the index entry has to say where the row is, and that it can be either a physical address or the primary-key value.

for a middle

Give one cost and one maintenance consequence per design: direct read but row-move repair, versus stable references but an extra tree descent.

for a senior

Discuss forwarding-pointer accumulation and when to reorganize, and the write amplification of primary-key updates across the index set.

for a principal

Turn it into schema policy - narrow immutable surrogate keys, index-count budgets, and reorganization thresholds tied to observed forwarded-row and lookup metrics.

## The problem both designs solve A secondary index must be able to get from a key to a row. It needs a locator, and there are only two families: point at *where the row is* (physical), or point at *what the row is* (logical, via the primary key). Every consequence below follows from that one choice. ## Physical locators Used with heap storage, the entry carries something like (file, page, slot). **Read path.** One page read gets the row. There is no second tree to walk, no key comparisons, no dependency on primary-key width. For selective queries this is the fastest lookup design there is. **Update path.** Rows are not always stable. Updating a variable-length column can make a row outgrow the free space on its page. Now it must move - and every secondary index entry pointing at the old address is wrong. Repairing all of them would make a single update proportional to the number of indexes, which is unacceptable, so engines use one of two mitigations: - **Forwarding pointers / migrated rows**: leave a stub at the original slot pointing to the new location. Secondary indexes stay untouched, but every lookup through them now costs two page reads. Accumulate enough of these and a table degrades measurably until reorganized. - **Version-chain designs**: keep the new version elsewhere and reach it from the original location, which has the same double-hop character. **Other properties.** Locators are small and fixed width, so index entries are compact and fanout is high. Insert order and physical order coincide, so an index on an append-ordered column tends to be well clustered. But any physical reorganization of the table invalidates every secondary index, so a reorganization implies rebuilding them. ## Primary-key locators Used with clustered / index-organized storage, where rows live in the leaves of the primary-key B+Tree. **Read path.** Two traversals: the secondary tree to find the key, then the clustered tree to find the row. On a large table that is perhaps three extra page reads per matched row instead of one - measurably more expensive, though the upper levels of the clustered tree are almost always cached, so the marginal cost in practice is often one or two real reads. **Update path.** Because the reference is logical, the row can move anywhere inside the clustered tree - page splits, defragmentation, whole-table reorganization - without touching any secondary index. That removes an entire class of maintenance. The symmetric cost: changing a primary-key *value* rewrites the locator in every secondary index, which is one strong argument for immutable surrogate keys. **Other properties.** Entries carry the full primary key, so a wide key inflates every secondary index, reducing fanout and buffer-pool efficiency. On the plus side, the primary-key columns are implicitly available in every secondary index, which lets more queries be answered without touching the table at all, and range queries on the primary key are automatically clustered. ## How to choose, in practice Usually you do not - the engine's storage model decides. What you control is how well you live with it: - On clustered storage, keep the primary key **narrow and immutable**. A compact surrogate key with a unique constraint on the natural key is the standard answer, precisely because the key is replicated into every secondary index and into every lookup comparison. - On heap storage, watch for **row growth patterns** - updates that fill previously empty variable-length columns are the classic generator of forwarding pointers - and schedule reorganization when the forwarded-row count climbs. - In both, remember the lookup can be avoided entirely when the index already holds every column the query needs, and that on clustered storage the primary key is free inside every secondary index. ## The interview shape A strong answer states the two locator families, gives one concrete cost each (one page read versus a tree descent), and names the maintenance consequence each design generates (forwarding pointers versus wide entries and primary-key-update fan-out). Candidates who only recite "clustered is faster" without the locator mechanism have memorized a conclusion rather than understood the structure.

  • Why would an engine leave a forwarding pointer instead of updating the secondary indexes when a row moves?
    Updating every index would make one row update cost proportional to the number of indexes on the table, turning a small write into many random index-page writes and log records. A forwarding stub keeps that update local to one or two pages. The debt is paid by readers, who follow an extra hop on every lookup through a secondary index until a reorganization removes the forwarding.
  • What operational rule follows from primary-key locators for schema design?
    Keep the primary key narrow and never update it. Narrow because the key is copied into every entry of every secondary index and compared on every lookup, so width multiplies across the whole index set. Immutable because changing the value rewrites the locator in every secondary index, which is an expensive and lock-heavy operation. That is the standard argument for a compact surrogate key plus a unique constraint on the natural key.

saying these in an interview costs you the question

  • Assuming all engines identify rows the same way
  • Not knowing that a moved row in a heap can leave a forwarding pointer that doubles lookup cost
  • Claiming clustered storage makes secondary-index lookups cheaper than a heap's direct address
  • Overlooking that a primary-key change rewrites entries in every secondary index
  • Ignoring that a wide primary key inflates every secondary index under clustered storage

context