PostgreSQL Serializable isolation does not turn every read into a blocking lock. Transactions still execute against MVCC snapshots, while the database tracks read-write dependencies that can make concurrent execution inconsistent with every possible serial order.

That distinction matters when an invariant spans multiple rows. Snapshot visibility can give each transaction a stable view and still permit a pair of writes whose combined result could not arise if the transactions had run one after another. Serializable Snapshot Isolation, or SSI, adds conflict detection around that snapshot model.

Snapshot stability is not serial order

Consider a table that records active reservations for a resource. Two transactions can each inspect the same snapshot, observe enough remaining capacity, and insert a different reservation. If neither transaction updates a row touched by the other, ordinary row-level write conflict detection has no direct collision to stop.

A stable snapshot therefore answers a visibility question: which committed row versions can this transaction see? Serializability adds a broader constraint: the committed outcome must correspond to some ordering in which complete transactions ran one at a time.

PostgreSQL implements its Serializable level by monitoring dependencies among concurrent transactions. It does not need to block every writer that could affect a prior query. Instead, it can allow work to proceed and abort a transaction when the observed dependency pattern threatens serial execution.

SIRead locks record read dependencies

Reads performed by Serializable transactions can create predicate-lock state visible in pg_locks with the mode SIReadLock. Despite the lock name, these entries are not conventional blocking read locks. A concurrent write is allowed to proceed.

The state gives PostgreSQL enough information to recognize that a write affected data previously read by another Serializable transaction. The tracked object can be a tuple, page, or relation, depending on the access path and available predicate-lock memory.

A sequential scan has broad coverage and requires relation-level predicate locking. Index access can permit finer tracking. This means query plans can affect the granularity of SSI conflict information even when two plans return the same rows.

Fine-grained entries can also be combined into coarser entries as PostgreSQL manages the finite memory reserved for predicate locks. Coarser tracking preserves correctness but can identify conflicts that finer tracking could have kept separate. The practical result can be more serialization failures under some workloads.

Conflict detection covers rows that did not exist

Serializable correctness cannot depend only on tuples that were visible during a read. A later insert may create a row that would have matched an earlier predicate.

Suppose a transaction checks:

SELECT count(*)
FROM reservations
WHERE resource_id = 42
  AND active;

Another concurrent transaction can insert a new active row for resource 42. If the first transaction makes a related write based on its earlier count, the interaction may form part of a serialization anomaly.

Predicate locking exists to represent this class of dependency. PostgreSQL tracks access to physical database objects in a form that lets later writes establish read-write conflicts. For B-tree scans, index-level predicate-lock handling can cover relevant key-space effects rather than treating only already-visible heap tuples as significant.

The mechanism is therefore different from taking SELECT ... FOR UPDATE locks on the rows returned by a query. Row locks protect existing rows. SSI must also detect writes whose presence would have changed an earlier read.

Dangerous dependency patterns trigger aborts

A single read-write dependency is not itself enough to prove that concurrent execution has no serial ordering. PostgreSQL watches combinations of dependencies associated with serialization anomalies and cancels a transaction when required to keep committed history serializable.

The application-visible consequence is a serialization failure, normally reported with SQLSTATE 40001. This is part of the isolation contract rather than an exceptional database malfunction. A transaction running at Serializable must be prepared for its work to be rejected because concurrent activity made that execution unsafe to commit.

Retry logic must restart the whole transaction. Reissuing only the statement that received the error does not recreate the transaction against a new consistent execution context.

Results obtained from a Serializable transaction that later aborts must not be treated as committed facts. The successful retry is the execution whose results can be used.

Read-only deferrable transactions can wait for a safe snapshot

Read-only work has a useful special case. A transaction declared SERIALIZABLE READ ONLY DEFERRABLE can wait before executing queries until PostgreSQL can provide a snapshot that is safe from serialization anomalies relevant to that transaction.

For reporting work that can tolerate startup delay, this changes the timing of the cost. Instead of beginning immediately and retaining the possibility of a later serialization failure, the transaction can wait for a suitable snapshot and then perform its reads without that risk.

The mode is intentionally narrow: it applies to Serializable, read-only, deferrable transactions. It is not a general switch that makes arbitrary read-write transactions immune to serialization failures.

Predicate-lock pressure can change failure rates

SSI bookkeeping consumes shared memory. PostgreSQL exposes settings including max_pred_locks_per_transaction, max_pred_locks_per_relation, and max_pred_locks_per_page to control predicate-lock capacity and promotion behavior.

When many fine-grained predicate locks must be represented more coarsely, unrelated writes can appear to conflict at the broader object level. Correctness remains intact because PostgreSQL errs toward rejecting a potentially unsafe execution rather than permitting an anomaly.

This makes serialization failure rate partly an operational signal. A workload with long transactions, broad scans, high concurrency, or frequent predicate-lock promotion can experience more retries even when application logic has not changed.

Serializable isolation is therefore more than a stronger snapshot setting. PostgreSQL combines MVCC execution, non-blocking read dependency tracking, and transaction aborts to constrain committed history to serial outcomes. The boundary to design around is explicit: concurrency can continue freely in many cases, but any transaction rejected with a serialization failure must be safe to run again from its beginning.