Warm-up

Reproduce all four anomalies

10 minjunior015 yrs

One concept, guided. Near-impossible to fail.

What this teaches

  • An isolation level is a rule about which versions a read can see
  • The standard states a minimum each level must prevent, not a ceiling
  • A lost update needs none of the three named anomalies to occur
  • Your database's default is probably not the one you assumed

Starter

Starter.java
import java.util.*;

/**
 * Warm-up: build a store just big enough to show why the levels differ.
 *
 * No database and no threads. Two transactions interleaved in an order you
 * write by hand — which is the only way to see these anomalies reliably,
 * because in a real system they depend on timing you do not control.
 */
public class Starter {

    enum Level { READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE }

    /** One value written by one transaction. Invisible to others until committed. */
    static final class Version {
        final String key; final int value; final long writer;
        long committedAt = Long.MAX_VALUE;
        Version(String key, int value, long writer) {
            this.key = key; this.value = value; this.writer = writer;
        }
    }

    public static void main(String[] args) {

        // TODO 1: build the store. A List<Version> and a counter that stands in
        // for time is enough. seed(key, value) adds an already-committed
        // version; begin(level) hands back a transaction stamped with the
        // current clock.

        // TODO 2: write read(key). This one method is the whole exercise —
        // every level is a different answer to "which versions may I see?":
        //
        //   READ_UNCOMMITTED  any version at all, committed or not
        //   READ_COMMITTED    any version committed by the time of THIS read
        //   REPEATABLE_READ   only versions committed before I started
        //   SERIALIZABLE      same, plus a check at commit time
        //
        // A transaction must always see its own uncommitted writes.

        // TODO 3: dirty read. Transaction A writes without committing;
        // transaction B reads. Run it at READ_UNCOMMITTED and READ_COMMITTED.
        // Say what B risks acting on if A later rolls back.

        // TODO 4: non-repeatable read. B reads, A writes and commits, B reads
        // the same key again. Run at READ_COMMITTED and REPEATABLE_READ.
        // Nothing broke a rule here — explain why that is the point.

        // TODO 5: phantom. Add readRange(prefix) returning matching keys. B
        // scans, A inserts a new key and commits, B scans again.
        //
        // Your REPEATABLE_READ will prevent this. The SQL standard PERMITS
        // phantoms at that level. Explain why both statements are correct, and
        // which real database behaves like your model.

        // TODO 6: lost update. A and B both read 100, both write 150, both
        // commit. Print the balance. Which of the three anomalies above did
        // this require? Read the question again — the answer is none.

        // TODO 7: make SERIALIZABLE refuse the second commit. Record what each
        // transaction read, and at commit time reject if anything it read was
        // written and committed by someone else after it began. Confirm B is
        // now rejected rather than silently overwriting A.

        // TODO 8: with B rejected, the balance is 150 and still not 200. Say
        // what the application must now do, and why serializable on its own
        // does not make the code correct.

        // TODO 9: run all four anomalies at all four levels and print the
        // matrix. Compare it to the table in any textbook. Two cells differ —
        // find them and explain both.

        // TODO 10: finally, check reality. Run the query for your own database
        // (SELECT current_setting('transaction_isolation') on PostgreSQL,
        // SELECT @@transaction_isolation on MySQL). Was it what you expected?
    }
}

Run it locally:

cd exercises/java/transactions/isolation-levels/01-warmup
javac Starter.java -d /tmp/out && java -cp /tmp/out Starter

Done when

  • You produced a dirty read, a non-repeatable read, a phantom and a lost update
  • You can say which level first prevents each one
  • You showed that snapshot reads prevent phantoms the standard permits
  • You checked what your own database actually defaults to

← Back to What are the isolation levels, and what does each permit?