PostgreSQL REPEATABLE READ serialization limits on concurrent updates
0 reputation · 05 Jun 2021, 08:43 UTC
0 reputation · 05 Jun 2021, 08:43 UTC
In PostgreSQL, the REPEATABLE READ isolation level utilizes Multiversion Concurrency Control (MVCC) to provide a consistent snapshot as of the start of the first query. While this implementation effectively prevents phantom reads—exceeding the standard SQL requirement—it introduces specific constraints regarding concurrent data modification.
When a transaction attempts to update a row that has been modified and committed by another concurrent transaction after the current transaction's snapshot began, the engine returns a 'could not serialize access' error (SQLSTATE 40001). This shifts the burden of conflict resolution to the application layer rather than relying on row-level waiting.
Under what specific scenarios does REPEATABLE READ fail to prevent write skew that the SERIALIZABLE level would otherwise catch? How does the behavior differ between a duplicate key error and a serialization error when evaluating unique index constraints under this isolation level?
34775 reputation · 05 Jun 2021, 14:43 UTC
PostgreSQL's REPEATABLE READ isolation level prevents phantom reads, but it does not prevent write skew. Write skew occurs when two concurrent transactions read the same set of data, but update different, disjoint rows in a way that violates a business constraint that depends on the combined state of those rows.
Imagine a requirement that at least one doctor must be on call. Two doctors, Alice and Bob, are both currently on call. Both start transactions in REPEATABLE READ mode:
Because they updated different rows, no row-level lock conflict occurs. Both transactions commit successfully, leaving zero doctors on call. The SERIALIZABLE level would prevent this by tracking read dependencies (SIREAD locks) and aborting one of the transactions with a serialization failure.
While both result in a failed operation, they trigger under different MVCC and indexing mechanisms.
This occurs when a transaction attempts to UPDATE or DELETE a row that was modified and committed by another transaction after the current transaction's snapshot was taken. In REPEATABLE READ, the engine cannot "merge" the changes into the existing snapshot, so it aborts the transaction to maintain consistency.
This occurs when a transaction attempts to INSERT or UPDATE a value that violates a UNIQUE index. Unlike serialization errors, unique constraints are enforced regardless of the snapshot time.
| Feature | Serialization Error (40001) | Duplicate Key Error (23505) |
|---|---|---|
| Trigger | Concurrent update to the same row | Violation of unique index |
| Behavior | Immediate abort of transaction | Wait for conflicting txn to commit/rollback |
| Cause | MVCC snapshot conflict | Physical index constraint |
To observe the serialization error, run these in two concurrent sessions (assuming PostgreSQL 12+):
-- Session 1
BEGIN ISOLATION LEVEL REPEATABLE READ;
UPDATE accounts SET balance = balance - 10 WHERE id = 1;
-- (Wait for Session 2 to commit)
COMMIT;
-- Session 2
BEGIN ISOLATION LEVEL REPEATABLE READ;
UPDATE accounts SET balance = balance - 20 WHERE id = 1;
COMMIT;
Session 1 will fail with could not serialize access due to concurrent update upon attempting to commit or update if Session 2 has already committed.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.