System designLoading progress…

Composite indexes

Explain how indexes change work and how transactions protect application invariants under concurrency.

Composite indexes

A composite index on (country, city, age) is sorted by country, then city, then age. A lookup can use the leading columns that the query constrains: country and city equalities use two; city alone uses none because the first column is free; a range ends the usable prefix. Column order is therefore a design decision, and the real behaviour of your database's optimizer should be confirmed with its execution plan.

Index on (country, city, age). Click a column to cycle its predicate: none → equals → range.

leading columns the index can use: 1 (country) the walk stopped at a gap or a range; the remaining predicates are filtered afterwards an index is judged by column order and the predicates you run, not by how many columns it has
Click the columns to change the predicates.

Predict first. Should a composite index's utility be judged only by its column count?

Chapter context

Databases execute concurrent requests against shared state. Fast lookup and correct updates are separate concerns: an index can improve a query while the application still loses updates or violates a cross-row rule.

Chapter reference: math, worked example & code

This section belongs to the whole chapter, so it is the same on every lesson in it and may cover a different lesson's topic. The code for the experiment you just ran is inside the experiment itself.

Math and notation

For an idealized search tree with effective fanout f and N distinguishable leaves, log base f of N gives a rough depth scale. Actual pages, occupancy and row-pointer costs differ. For an optimistic update, change value and version only when the stored version equals the version read by the caller.

Worked example

Two callers read count=10 at version 1 and each intends to increment. Unconditional writes of 11 lose one increment. With an atomic expected-version check, the first writes 11/version 2; the second stale attempt is rejected. After rereading, it can write 12/version 3. The model below demonstrates the condition, not database transaction implementation.

Reference code

The following example uses the Python standard library and does not call a remote service.

state={'count':10,'version':1}
def update(expected):
    if state['version'] != expected: return False
    state['count']+=1
    state['version']+=1
    return True
assert update(1)
assert not update(1)
assert update(2)
assert state == {'count':12,'version':3}

Python examples are provided for study and local execution; this page does not execute Python.

Common mistakes

A transaction's isolation level matters. A unique application-side check is race-prone without a database-enforced invariant. Index depth estimates are not actual query-latency measurements.