PG.L6.DEFAULT_TRANSACTION_ISOLATION_DRIFT — a new error class your code has no reason to handle
- Category: safety
- Level: 6
- Confidence: heuristic
- Downtime class: none
- Stability: stable
- Suites: audit
- Applies to: PostgreSQL 18
read committed is PostgreSQL's default, and it is what Laravel's transaction handling is written
against. Raising the server-wide default to repeatable read or serializable changes what every
transaction does — including the ones the framework opens on your behalf, which no application code
ever looks at.
At a higher level a transaction can fail with a serialization failure (SQLSTATE 40001) instead
of blocking. That error is designed to be retried. Code written against the defaults has no reason to
retry it, because at read committed it never happens.
So the failure appears under concurrency, in production, in a code path nobody wrote error handling for — and not once in development.
Why the finding is worded as a consequence, not a mistake
An application that deliberately runs serializable and has written the retry handling to match is
the expected exception, not a defect. Raising isolation is a legitimate, sometimes necessary
choice; what makes it a finding is that the choice was made server-wide, where the code that has to
cope with it cannot see it.
That case belongs on the ignore list, with the reason recorded there:
'ignore' => [
['rule' => 'PG.L6.DEFAULT_TRANSACTION_ISOLATION_DRIFT', 'reason' => 'the ledger service runs serializable and retries 40001'],
],
Server value, not session value
The check reads the server's default — what a connection gets when nothing else has spoken. A connection that raised its own level is doing something local and deliberate, and produces no server finding.
Flagged
ALTER SYSTEM SET default_transaction_isolation = 'serializable';
SELECT pg_reload_conf();
Preferred
ALTER SYSTEM SET default_transaction_isolation = 'read committed';
SELECT pg_reload_conf();
Then raise it where you need it — SET TRANSACTION ISOLATION LEVEL SERIALIZABLE at the start of the
transaction that actually requires it, next to the retry loop that handles the consequence.
Sources
- PostgreSQL 18 — transaction
isolation —
read committedis the default; the higher levels can abort a transaction with a serialization failure - PostgreSQL 18 — error codes —
serialization_failureis40001, in the transaction-rollback class - PostgreSQL 18 — client connection defaults — the variable is settable per session and per role, so the server value is only the fallback