Warm-up

Enter the index from the wrong side

10 minjunior110 yrs

One concept, guided. Near-impossible to fail.

What this teaches

  • A composite index is sorted by the first column, then the second
  • You may skip columns from the right, never from the left
  • (a, b) and (b, a) are different indexes, not two spellings of one
  • A full index scan still shows the index in a plan, and is not a seek

Starter

Starter.java
import java.util.*;

/**
 * Warm-up: the same two columns, both ways round.
 *
 * A model, not a database. The only thing modelled is that a composite index
 * is sorted by its first column, then its second — which is the whole of the
 * leftmost prefix rule.
 */
public class Starter {

    static final int ROWS = 1_000_000;
    static final int CUSTOMERS = 250_000;

    static int customerOf(int row) { return (int) ((row * 7919L) % CUSTOMERS); }
    static int statusOf(int row)   { return (int) ((row * 31L) % 4); }

    /** Leading column in the high bits, so sorting sorts by column order. */
    static long[] build(boolean customerFirst) {
        long[] e = new long[ROWS];
        for (int row = 0; row < ROWS; row++) {
            long a = customerFirst ? customerOf(row) : statusOf(row);
            long b = customerFirst ? statusOf(row) : customerOf(row);
            e[row] = (a << 40) | (b << 20) | row;
        }
        Arrays.sort(e);
        return e;
    }

    static long lead(long e)   { return e >>> 40; }
    static long second(long e) { return (e >>> 20) & 0xFFFFF; }

    public static void main(String[] args) {
        long[] customerStatus = build(true);
        int customer = 12_345;

        // The seek: binary search on the LEADING column.
        int lo = 0, hi = ROWS, comparisons = 0;
        while (lo < hi) {
            int mid = (lo + hi) >>> 1;
            comparisons++;
            if (lead(customerStatus[mid]) < customer) lo = mid + 1; else hi = mid;
        }
        int matched = 0, c = lo;
        while (c < ROWS && lead(customerStatus[c]) == customer) { c++; matched++; }

        System.out.println("(customer_id, status), WHERE customer_id = ?");
        System.out.println("  " + comparisons + " comparisons, " + matched + " entries");

        // TODO 1: now try WHERE status = 1 against the SAME index. You cannot
        // binary search for it — write the loop that finds the matches and
        // count how many entries you had to examine. Predict the number first.

        // TODO 2: state the rule you just demonstrated in one sentence, using
        // the phrase "leftmost prefix".

        // TODO 3: build the reversed index with build(false) and run both
        // queries against it. Fill this in:
        //
        //                          (customer_id, status)   (status, customer_id)
        //   WHERE customer_id = ?        ____                    ____
        //   WHERE status = ?             ____                    ____
        //
        // Two of the four are seeks and two are full scans.

        // TODO 4: a real database sometimes chooses the full index scan
        // anyway, and the plan then names the index. Say why that makes this
        // bug harder to spot in a review than a missing index would be.

        // TODO 5: which single index would serve BOTH queries with a seek?
        // Answer honestly — the answer is not one index.
    }
}

Run it locally:

cd exercises/java/indexing/composite-index-order/01-warmup
javac Starter.java -d /tmp/out && java -cp /tmp/out Starter

Done when

  • You seeked on the leading column and counted the comparisons
  • You tried the second column alone and counted what it cost
  • You built the reversed index and showed which query each one serves
  • You can state the leftmost prefix rule in one sentence

← Back to Does an index on (a, b) help a query filtering only on b?