Predict the output
Both sessions read, then each updates its own row, under REPEATABLE READ. What does the last line print?
import psycopg
from psycopg import IsolationLevel
with psycopg.connect(autocommit=True) as c:
c.execute("DROP TABLE IF EXISTS doctors")
c.execute(
"CREATE TABLE doctors (name text PRIMARY KEY, on_call bool)"
)
c.execute("INSERT INTO doctors VALUES ('ana', true), ('ben', true)")
a, b = psycopg.connect(), psycopg.connect()
a.isolation_level = b.isolation_level = IsolationLevel.REPEATABLE_READ
for conn, name in ((a, "ana"), (b, "ben")):
conn.execute(
"SELECT count(*) FROM doctors WHERE on_call"
) # both see 2
for conn, name in ((a, "ana"), (b, "ben")):
conn.execute(
"UPDATE doctors SET on_call = false WHERE name = %s", (name,)
)
a.commit()
b.commit()
row = (
psycopg.connect()
.execute("SELECT count(*) FROM doctors WHERE on_call")
.fetchone()
)
print(row)Hint 1 of 2
Which rows did each session write?
Hint 2 of 2
Snapshot isolation compares writes, not reads.
Choose an answer, then open Show answer below to check it.
Run on PostgreSQL 17.11 (two sessions)
Text version
READ COMMITTED:
- A: SELECT count(*) … WHERE on_call (sees 2)
- B: SELECT count(*) … WHERE on_call (sees 2)
- A: UPDATE … SET on_call = false WHERE name = 'ana' (own row)
- B: UPDATE … SET on_call = false WHERE name = 'ben' (own row)
- A: COMMIT (ok)
- B: COMMIT (ok)
REPEATABLE READ:
- A: SELECT count(*) … WHERE on_call (sees 2)
- B: SELECT count(*) … WHERE on_call (sees 2)
- A: UPDATE … SET on_call = false WHERE name = 'ana' (own row)
- B: UPDATE … SET on_call = false WHERE name = 'ben' (own row)
- A: COMMIT (ok)
- B: COMMIT (ok)
SERIALIZABLE:
- A: SELECT count(*) … WHERE on_call (sees 2)
- B: SELECT count(*) … WHERE on_call (sees 2)
- A: UPDATE … SET on_call = false WHERE name = 'ana' (own row)
- B: UPDATE … SET on_call = false WHERE name = 'ben' (own row)
- A: COMMIT (ok)
- B: COMMIT (ERROR 40001, retry)
| Level | Result | On call |
|---|---|---|
| READ COMMITTED | ✕ Happens | 0 |
| REPEATABLE READ | ✕ Happens | 0 |
| SERIALIZABLE | ↻ 40001, retry | 1 |
Show answer
Answer: A (0,)
Correct. Both commits succeed: they wrote different rows, so snapshot isolation sees no conflict. Nobody is on call.
REPEATABLE READ only detects two transactions writing the same row. Here each changed a different row based on a count the other made stale, which is write skew, so both commit.
Why not the others?
- B
(1,). That's what SERIALIZABLE gives: one commit fails with 40001, so one doctor stays on call. - C
(2,). Both updates did commit; neither was rolled back. - D The second
commit()raises SerializationFailure. REPEATABLE READ only raises 40001 when two transactions update the same row.