PostgreSQL Fundamentals

Transaction Isolation Levels

Learn what transaction isolation levels mean for PostgreSQL teams and how Vela branches help you test isolation-sensitive queries and migrations safely.

Definition

Transaction isolation levels define how visible the changes made by one transaction are to other concurrent transactions, trading consistency guarantees against performance and concurrency.

Key takeaway: Choosing the right isolation level is a correctness decision — too low and agents or queries see stale or inconsistent data; too high and contention rises.

Transaction isolation levels are the knob PostgreSQL gives you to control how much concurrent activity affects the data a transaction sees. Every database transaction runs at some isolation level — the default is READ COMMITTED — and that choice determines which consistency anomalies the application needs to handle.

Key Facts Transaction Isolation Levels
Type Concurrency control
Layer Per-transaction setting
Mechanism Snapshot + predicate locks
Risk solved Stale or inconsistent reads

The isolation level a transaction uses is not just a performance dial. It is a correctness decision. Code that reads data, makes a decision based on what it read, and then writes a result can produce wrong answers if the underlying data changes between the read and the write — a class of bugs that only appears under concurrency and only at certain isolation levels.

Transaction Isolation Levels explainer: Concurrent Transactions and agentic workloads pass through the Isolation Level engine, producing Anomaly Protection and consistent results

What Transaction Isolation Levels Mean

The SQL standard defines four isolation levels in terms of which anomalies they permit:

READ UNCOMMITTED — allows dirty reads (seeing uncommitted changes from other transactions). PostgreSQL does not implement this level: it maps READ UNCOMMITTED to READ COMMITTED, so dirty reads cannot occur.

READ COMMITTED — the PostgreSQL default. A new snapshot is taken at the start of each SQL statement within the transaction. Two SELECT statements in the same transaction can return different results if a concurrent transaction commits between them.

REPEATABLE READ — the snapshot is taken once at the start of the first query in the transaction and held for the entire transaction. Reads are stable. PostgreSQL’s MVCC implementation also prevents phantom reads at this level. Concurrent transactions that write to the same rows will cause one to be aborted with a serialization error.

SERIALIZABLE — builds on REPEATABLE READ by adding predicate locking. PostgreSQL tracks the data ranges each transaction read and detects whether any concurrent transaction’s writes could logically have changed the read result. If a conflict is detected, one transaction is aborted with ERROR: could not serialize access due to read/write dependencies. The application must retry.

Where Isolation Levels Matter in Practice

Agentic workloads — AI agents that query data, reason over it, and write back a decision or state update are read-modify-write patterns. Under READ COMMITTED, the data an agent read in step one may have changed before the agent writes in step three, resulting in a decision based on stale context. REPEATABLE READ prevents this by locking the agent’s view to a stable snapshot for the full transaction.

Analytics and reporting — long-running queries that join multiple tables need a consistent snapshot to produce meaningful aggregates. READ COMMITTED can return a mix of data from before and after a concurrent batch write. Running reports under REPEATABLE READ ensures the result reflects a single consistent state.

Schema migrations — a migration that reads existing rows to backfill a new column should use REPEATABLE READ to ensure the read and write phases operate on the same consistent dataset. Under READ COMMITTED, rows committed by other sessions after the migration begins may be missed or double-processed.

Test isolation-sensitive queries in a Vela branch before changing production settings. Try Database Branching

Why Isolation Levels Matter for Production Postgres

The default READ COMMITTED level is correct for most simple OLTP operations: short-lived transactions that write a single row rarely encounter anomalies. Problems appear when:

  • Transactions are longer and span multiple reads and writes
  • Application logic makes decisions based on read data before writing
  • Concurrent batch processes update large sets of rows
  • AI agents or automation scripts treat a Postgres query result as authoritative input to a subsequent action

Choosing a higher isolation level introduces retry complexity. SERIALIZABLE transactions can be aborted by PostgreSQL when a conflict is detected. The application must detect SQLSTATE 40001 (serialization failure) and retry the transaction from the beginning. Systems that use SERIALIZABLE need retry logic baked into their database access layer.

Isolation LevelDirty readNon-repeatable readPhantom readSerialization anomaly
READ COMMITTEDPreventedPossiblePossiblePossible
REPEATABLE READPreventedPreventedPrevented (in PG)Possible
SERIALIZABLEPreventedPreventedPreventedPrevented

How Isolation Levels Relate to Vela

Isolation level selection is easy to get wrong in staging because staged environments rarely replicate the concurrency patterns of production. A READ COMMITTED application that works fine in a single-user test environment can produce wrong results under production-level concurrent writes.

Vela branches give teams a production-like Postgres environment where isolation-sensitive queries can be validated against realistic data volumes and concurrent load patterns before changes ship. This is particularly useful when:

  • Changing the default isolation level for a service or migration job
  • Introducing agentic or AI workloads that read and write in the same transaction
  • Verifying that SERIALIZABLE retry logic works correctly before deploying it

The branch starts from a consistent snapshot of the source database, so the isolation semantics the branch Postgres instance provides are the same as production. Changes made in the branch — including SET default_transaction_isolation experiments — are isolated from the source.

Operational Checks

  • Check the current default with SHOW default_transaction_isolation; and confirm it matches application expectations
  • Add retry logic for SQLSTATE 40001 before enabling SERIALIZABLE on any critical path
  • Review long-running transactions with SELECT * FROM pg_stat_activity WHERE state = 'active' AND now() - query_start > interval '1 minute'
  • Confirm agentic and read-modify-write workflows use REPEATABLE READ or SERIALIZABLE explicitly

Start with How Vela Works, Database Branching, Branch per PR, and the Vela articles library. For adjacent glossary terms, review Snapshot Isolation, MVCC (Multi-Version Concurrency Control), Transaction, and Deadlock Detection and Prevention.

Frequently Asked Questions

What are transaction isolation levels?
Transaction isolation levels are a standard SQL mechanism for controlling how much one transaction can see of another concurrent transaction's uncommitted or committed changes. The SQL standard defines four levels — READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ, and SERIALIZABLE — each offering progressively stronger consistency guarantees at the cost of increased contention or retry complexity.
What isolation levels does PostgreSQL actually implement?
PostgreSQL maps READ UNCOMMITTED to READ COMMITTED — dirty reads are never allowed regardless of the requested level. In practice PostgreSQL provides three effective levels: READ COMMITTED (default, per-statement snapshot), REPEATABLE READ (transaction-level snapshot, prevents non-repeatable reads and phantoms), and SERIALIZABLE (adds predicate locking to prevent all serialization anomalies).
How do you set the isolation level for a transaction in PostgreSQL?
Set the level immediately after BEGIN: BEGIN ISOLATION LEVEL REPEATABLE READ; or use SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; as the first statement in the transaction. To change the default for a session use SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL REPEATABLE READ;. To change the cluster default, set default_transaction_isolation in postgresql.conf.
Which isolation level should AI agent workloads use?
AI agents that read data, reason about it, and then write back decisions are vulnerable to write-skew anomalies under READ COMMITTED — the data the agent read may have changed by the time it writes. REPEATABLE READ gives the agent a stable view and detects conflicting concurrent writes. SERIALIZABLE provides the strongest guarantee but requires the application to handle serialization failures by retrying the transaction.
What anomalies does each isolation level prevent?
READ COMMITTED prevents dirty reads only. REPEATABLE READ prevents dirty reads, non-repeatable reads, and phantom reads (PostgreSQL prevents phantoms even at this level due to its MVCC snapshot semantics). SERIALIZABLE prevents all of the above plus write-skew and serialization anomalies by using predicate locking in addition to snapshot isolation.