Skip to main content
Database Isolation Levelslesson 3 of 4 · 2 min read

Repeatable Read and Snapshots

Answering from a snapshot

Repeatable Read moves the snapshot boundary from the statement to the transaction. Your first read fixes a view of the database, and every read after it sees that same frozen moment however much commits around you.

That buys you something specific. Rows you have read stay stable. A long report now gets numbers that agree with each other instead of a smear across several minutes of other people's activity.

For readers this costs almost nothing, because the version chains already exist and your transaction simply keeps reading the versions visible when it began.

One wrinkle if you move between databases. Postgres implements this as full snapshot isolation. MySQL, where it is the default, reads the snapshot for plain queries but shows locking reads and updates the latest committed data instead, which surprises people porting code across.

Where the level shows teeth

Writes are where the level shows teeth. Try to update a row another transaction changed and committed after your snapshot began, and Postgres aborts you with a serialization error rather than letting you overwrite history.

Catch it and retry, because your application has to. It is the first place many teams discover their code has no retry loop.

The limitation has a name: write skew. Two transactions read overlapping data, each writes something the other did not read, and both commit happily, yet together they break an invariant neither could see breaking alone.

The textbook case is two doctors both going off call, because each checked that the other was still on. Snapshot isolation cannot catch it, since neither transaction touched a row the other wrote. That gap is what Serializable exists to close, and it is a favourite senior interview question for exactly that reason.

the shape of it
Transaction opensSnapshot takenAnother commitFirst readSecond read1. as of nowignored2. same value3. same value
step 1 of 2
Every read answers from the snapshot, so the same query twice gives one answer.

Worked example

A hospital scheduling system requires at least one doctor on call per shift. Doctors Alice and Bob are both on call tonight, both feeling ill at 6pm. Each opens the app, which runs a Repeatable Read transaction: count doctors on call for tonight, see 2, conclude one can leave, and update their own row to off-call. The two transactions read the same snapshot, write different rows, and both commit without conflict. At 6:01 the shift has zero doctors, an outcome each transaction individually verified was impossible. That is write skew. The team's fix moves this one operation to Serializable, where Postgres detects the dangerous overlap and aborts one transaction; Bob's app retries, recounts, now sees 1 doctor on call, and tells him he has to stay.