lock_timeout

Maximum time to wait for a lock. Default 0 = wait forever. Too low: cancels legitimate DDL. Too high: lock-queue pileup blocks all readers.

MEDIUM RISK
Safe first move

0 (default, no limit). Set 5s-30s for APIs where lock waits should fail fast.

Main risk

Too low (500ms): ALTER TABLE canceled while waiting for read lock to release (lab-captured: 'canceling statement due to lock timeout'). Too high (0): lock queue can pile up.

Tier 1 — Lab-Proven — Measured behavior and safety boundaries are available below.

What We Observed

mechanism

lock_timeout cancels any lock request that waits longer than the timeout. Differs from statement_timeout: only fires during lock acquisition, not during query execution.

from lt effect

blocker holds ACCESS EXCLUSIVE on lt_demo: AccessExclusiveLock|t

Waiter tries SELECT (needs ACCESS SHARE, conflicts with the held ACCESS EXCLUSIVE):

  waiter lock_timeout=2s   | CANCELED (lock timeout)    | waited=2.3s
  (lock_timeout=0 would wait forever; not run here to avoid hanging the lab.)

Now release the blocker; the waiter is no longer blocked and acquires the lock:

  waiter lock_timeout=2s   | acquired count=1000        | waited=0.4s

Session survives the cancel? (cancel on lock wait, then run SELECT 1 in the SAME session)

ERROR:  canceling statement due to lock timeout

session still alive: 1

(lock_timeout bounds the WAIT for a lock with a non-fatal ERROR; the session keeps working. It does NOT bound work done AFTER the lock is acquired -- that is statement_timeout.)

live verification

1000ms lock

CANCELED at timeout

30000ms

allowed to complete

0 no limit

waits forever

Reproduced Danger

Sizing / Safe Range

Workload Guidance

Unlock Incident Pro

The free sections tell you what to do. Pro is the measured proof — the exact failure signature, safe-ceiling formula, and per-workload defaults, each captured on real PG 14–18 runs.

  • Reproduced Danger
  • Sizing / Safe Range
  • Workload Guidance
Unlock Incident Pro

lock_timeout sets the maximum time a statement will WAIT to acquire a lock before giving up. If the statement cannot get a lock it needs within the limit, the backend cancels it with a NON-FATAL error — ERROR: canceling statement due to lock timeout — aborts that statement (and its transaction), but the SESSION stays connected. The default is 0, meaning a statement will wait indefinitely for a lock. Crucially, lock_timeout bounds ONLY the time spent waiting for a lock, NOT the time the statement runs once it has the lock (that is statement_timeout). Its highest-value use is protecting schema changes: an ALTER TABLE (or any DDL) that needs an ACCESS EXCLUSIVE lock will wait behind any open transaction, and while it waits it sits at the head of the lock queue and blocks every new query on that table — so a single migration stuck behind a slow transaction can freeze all reads and writes of the table. Setting a small lock_timeout on the migration makes it fail fast and retry instead of holding the queue and taking the table down. The value can be set per session, role, database, or cluster-wide, and takes effect immediately because context=user.

name

lock_timeout

setting default

0 (disabled — wait forever)

unit

ms

vartype

integer

category

Client Connection Defaults / Statement Behavior

context

user

min val

0

max val

2147483647

enumvals

(n/a — integer ms)

boot val

0

reset val

0

pending restart

false

short desc

Sets the maximum allowed duration of any wait for a lock.

ground truth note

Captured literally from pg_settings on PG 14-18 (-18) on 2026-06-20. setting=0, boot_val=0, reset_val=0 on EVERY version (default_changed=false) — the guard ships DISABLED (wait forever). context=user (session-changeable, no reload/restart). vartype=integer, unit=ms, min_val=0 (0 disables), max_val=2147483647. category='Client Connection Defaults / Statement Behavior', short_desc='Sets the maximum allowed duration of any wait for a lock.' Identical across 14, 15, 16, 17, 18.

Common Pitfalls

  • Leaving lock_timeout at 0 for online DDL — a blocked ALTER TABLE camps at the head of the lock queue and freezes every read and write on the table.
  • Assuming lock_timeout caps how long a statement RUNS — it caps only the WAIT for a lock; a statement that acquires its lock then runs long is unbounded (use statement_timeout).
  • Setting a tight lock_timeout globally — ordinary statements that hit normal brief contention start failing with 'canceling statement due to lock timeout'.
  • Forgetting that a long DDL still blocks the table while it HOLDS the lock — lock_timeout shortens the wait, not the hold; keep DDL transactions short.
  • Not retrying after a lock_timeout cancel on a migration — the whole point is fail-fast-and-retry; without retry the migration just errors out.
  • Confusing it with deadlock_timeout — that controls when the deadlock detector wakes, not how long a statement waits for a non-deadlocked lock.

Evidence and References

  • PostgreSQL docs: Runtime Config — Client Connection Defaults / Statement Behavior (lock_timeout — abort any statement that waits longer than the limit to acquire a lock; 0 disables; bounds the wait, not the run time; related statement_timeout, deadlock_timeout).
  • PostgreSQL docs: Explicit Locking / table-level lock modes and the lock conflict matrix (why an ACCESS EXCLUSIVE request queues, and why later compatible requests queue behind it — the basis of the pileup).
  • PostgreSQL docs: pg_locks and pg_stat_activity (wait_event_type=Lock) — diagnosing a lock-queue pileup (one ungranted strong lock with requests queued behind it).
  • PostgreSQL docs: statement_timeout and transaction_timeout (the run-time and transaction-duration caps that complement lock_timeout's wait-only scope; transaction_timeout new in PG 17).
  • Lab capture 2026-06-20: — a waiter canceled at ~2.3s under lock_timeout=2s while a blocker held ACCESS EXCLUSIVE, session survived ; a queued ALTER TABLE froze a compatible SELECT (statement_timeout=3s hit) until given lock_timeout=1s, after which reads flowed, and lock_timeout=1s did NOT cancel a 4s pg_sleep that statement_timeout=1s did ; boundary min=0 disables, range error on -1, context=user, identical /; pg_settings ground truth PG 14-18 (-18).

Technical Reference

Category
Client Connection Defaults / Statement Behavior
Type
integer (ms (milliseconds; 0 disables))
Blast radius
query
Severity
Moderate
Context
user
Versions
14, 15, 16, 17, 18
Reviewed
2026-06-20

Severity scale: 1 Minimal · 2 Low · 3 Moderate · 4 High · 5 Critical.

Keep going

Related & next steps