PostgreSQLIsolation Levels

Isolation Levels

Isolation levels exist because "concurrent transactions shouldn't interfere with each other" is not a single, fixed rule — it's a spectrum. Stronger isolation gives stronger guarantees about what one transaction can observe of another's in-progress or recently-committed work, but typically at the cost of more blocking or more transactions having to retry. PostgreSQL lets you choose the level a transaction runs at, so you can pick the right trade-off for the task at hand.

Setting the isolation level

SQL
BEGIN;
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;

-- ... statements run at REPEATABLE READ for this transaction only ...

COMMIT;
The levels PostgreSQL supports

Level

Behavior

Read Committed (default)

Each statement within the transaction sees a fresh snapshot as of when that statement started — so a later statement in the same transaction can see rows committed by other transactions in the meantime

Repeatable Read

The entire transaction sees one single snapshot, taken as of its first statement — every statement within it sees exactly the same data, no matter what else commits in the meantime

Serializable

Behaves as if every transaction ran one at a time, in some order — the strongest level; PostgreSQL detects cases where true one-at-a-time behavior couldn't be preserved and forces one of the conflicting transactions to fail and retry

Note
The SQL standard also defines a Read Uncommitted level, which would allow reading another transaction's uncommitted, in-progress changes ("dirty reads"). PostgreSQL does not implement it — requesting READ UNCOMMITTED is silently treated as READ COMMITTED instead. This is a genuinely PostgreSQL-specific detail: because of MVCC, a transaction only ever sees committed row versions in the first place, so a true dirty read is architecturally impossible regardless of what isolation level you ask for.
Which anomalies each level prevents

Anomaly

Read Committed

Repeatable Read

Serializable

Dirty read (seeing uncommitted data)

Prevented

Prevented

Prevented

Non-repeatable read (a re-read row changes value)

Possible

Prevented

Prevented

Phantom read (a re-run query returns different rows)

Possible

Prevented*

Prevented

Serialization anomaly (result inconsistent with any one-at-a-time ordering)

Possible

Possible

Prevented

Note
*PostgreSQL's REPEATABLE READ also prevents phantom reads, which is actually stricter than the SQL standard technically requires at that level.
Choosing a level
Tip
Read Committed — the default — is fine for the vast majority of applications; most queries and transactions simply don't depend on the fine-grained anomalies it allows. Reserve Serializable for the specific cases that genuinely need the strongest possible guarantee against subtle concurrency anomalies — for example, enforcing an invariant across multiple rows that concurrent transactions could otherwise violate despite each individual statement looking correct in isolation. Just be ready to handle serialization failures by retrying the transaction, since that's how PostgreSQL enforces the guarantee.
  • Read Committed (default) — each statement sees the latest committed data as of when it runs.

  • Repeatable Read — the whole transaction sees one consistent snapshot throughout.

  • Serializable — the strongest level; behaves as if transactions ran one at a time.

  • PostgreSQL has no true Read Uncommitted — it is treated identically to Read Committed, since MVCC makes dirty reads impossible.

  • Set the level per-transaction with SET TRANSACTION ISOLATION LEVEL.