PG.L3.MISSING_STATEMENT_TIMEOUT — Bounding how long a step may hold what it took
- Category: safety
- Level: 3
- Confidence: deterministic
- Downtime class:
blocking - Stability: stable
- Suites: lint
- Applies to: PostgreSQL 18
lock_timeout protects you from a lock you cannot get. statement_timeout protects you from one you
did.
Once a strong-lock operation has acquired its ACCESS EXCLUSIVE lock, it holds it for as long as the
work takes — and everything else waits, for exactly as long. A table rewrite that was estimated at
thirty seconds and turns out to take twenty minutes is not a slow deploy; it is twenty minutes of
outage, and nothing stops it on its own.
statement_timeout is what caps the damage a single deploy step can do.
Why an estimate is not a bound
The two rules exist separately because the failures are separate. Estimates come from the row count
you had when you wrote the migration, from a staging database an order of magnitude smaller, from a
bigint change you thought was a metadata change. Every one of those is a reasonable belief that can
be wrong by a factor of a hundred.
A timeout does not need the estimate to be right. It needs the ceiling to be tolerable.
Flagged
DB::statement('ALTER TABLE orders ADD COLUMN notes text');
Preferred
DB::statement("SET lock_timeout = '3s'");
DB::statement("SET statement_timeout = '30s'");
DB::statement('ALTER TABLE orders ADD COLUMN notes text');
Set it high enough to be honest
A statement_timeout shorter than the work genuinely needs turns a long migration into a migration
that never completes, retried forever. If a step really does need ten minutes under a lock, the
answer is not a ten-minute timeout — it is a different migration strategy (add-backfill-swap), and
the timeout stays as the ceiling for when that goes wrong too.
SQLens sets its own, and never yours
Worth saying plainly on this page: the audit and catalog readers bound their own sessions with these settings before touching anything. What SQLens will not do is change your application's connection — its reader runs on a separate named connection built from a copy of your config, so the session it seals is its own.
Sources
- PostgreSQL 18 — client connection
defaults —
statement_timeoutaborts a statement that runs longer than the set duration; it bounds a different failure thanlock_timeout, which bounds only the wait to acquire
The fix material this rule carries
A finding from this rule carries machine-readable fix material, using this sequence:
timeout_preamble— Bound how long the statement may wait and how long it may run before it is given up on.
The payload is material for you or an agent to apply. SQLens writes no migration and runs no DDL. See the remediation payload for every field, the placeholder semantics, and the version rules a consumer has to follow.