T1row 1v1100v270v340v4−60row 2v1100v20T21-- READ COMMITTED2BEGIN;341-- READ COMMITTED2BEGIN;34
Back row: session T1, the versions of rows 1 and 2, session T2Front: the SQL each session sends, its isolation level on line 1Dashed frame: a snapshot · puck: a value read · envelope: a write · red badge: error 40001At the back: session T1 (a report), the two rows of the acct table as shelves of versions (oldest on the left), and session T2 (a transfer). In front: the SQL each session sends; line 1 is its isolation level. An UPDATE never overwrites a row: it adds a version, marked with a clock until it commits. A dashed frame is a session's snapshot, the versions it can see. Read Committed takes a new snapshot for every statement, Repeatable Read one for the whole transaction, and Serializable also refuses a commit (error 40001) that no one-at-a-time order could explain. Old row versions are not kept forever: once no transaction can still see them, VACUUM removes them.

Two at once

  1. T1 is a report that reads a balance. T2 is a transfer that changes it. Both run at the same time on the same row. The isolation level decides how much each one sees of the other’s work.

Read Committed

Repeatable Read

Serializable

What happens

;; step 1/11 · two
CREATE TABLE acct (id int PRIMARY KEY, bal int);
INSERT INTO acct VALUES (1, 100), (2, 100);

T1  a report: reads the balance of row 1
T2  a transfer: changes the balance of row 1
both are transactions: BEGIN ... COMMIT
they run at the same time, in two sessions

isolation level = how much of the other
transaction’s work each one can see

T1 and T2 are two database sessions. In the Repeatable Read and Serializable parts each one starts a fresh transaction with BEGIN; the names stay the same to keep the picture simple. In Read Committed an UPDATE does not fail like this: it waits for the other writer, then works on the newest committed version of the row.