A lock wait can be healthy coordination or an expensive queue. PostgreSQL’s lock_timeout lets an operation stop waiting, but it does not diagnose why the holder is slow or decide whether retrying is appropriate. Choose the timeout from the operation’s latency budget, and inspect the blocking transaction before increasing the limit or multiplying attempts.
When it breaks
lock_timeout applies separately to each lock acquisition attempt, including implicit locks. It measures time waiting for that lock, not all time spent by the statement. A query already doing useful work can still hit the limit when it later requests another lock. Several shorter waits can also accumulate beyond one configured interval. Treating this as a whole-query deadline leaves that accumulation uncontrolled.
statement_timeout bounds statement execution, including lock waiting. If it is shorter, it can cancel the statement before the lock timeout fires. idle_in_transaction_session_timeout addresses a different problem: a session waiting for its next client command while keeping a transaction open. A backend blocked inside an update is ordinarily active, with a lock wait event; calling every blocked connection idle in transaction directs diagnosis at the wrong sessions.
PostgreSQL 18 also has transaction_timeout, which terminates a session whose transaction lasts too long. That is a different failure boundary from canceling one statement with lock_timeout. A connection pool must discard a terminated connection. Neither setting is an application-wide deadline across several attempts; time already spent before the transaction and during retry backoff still belongs in the caller’s budget.
Check it quickly
Open two fresh psql -X -v ON_ERROR_STOP=1 sessions, A and B, against the same disposable PostgreSQL 18 database. Use the same role with schema creation rights, an unused timeout_demo schema, and autocommit outside explicit transactions. Start with the default lock_timeout of zero and no conflicting timeout settings. The short durations below make the demonstration fast; they are not production recommendations. A creates the fixture:
CREATE SCHEMA timeout_demo;
CREATE TABLE timeout_demo.counter (id integer PRIMARY KEY,n integer);
INSERT INTO timeout_demo.counter VALUES (1,0);
A updates the row inside an open transaction and keeps its lock:
BEGIN;
UPDATE timeout_demo.counter SET n=10 WHERE id=1;
B sets a one-hundred-millisecond lock-wait limit for its conflicting update; this is not a strict client-observed wall-clock bound. Temporarily allowing expected errors also exposes the transaction state before rollback:
BEGIN;
SET LOCAL lock_timeout='100ms';
SET LOCAL statement_timeout='1s';
\set ON_ERROR_STOP off
UPDATE timeout_demo.counter SET n=n+1 WHERE id=1;
\echo lock_state :SQLSTATE
SELECT n FROM timeout_demo.counter WHERE id=1;
\echo transaction_state :SQLSTATE
\set ON_ERROR_STOP on
ROLLBACK;
These are expected results for the unchanged two-session exercise; this review did not rerun concurrent sessions. Expect lock_state 55P03, then transaction_state 25P02. The first error cancels this statement and leaves the explicit transaction failed. B’s rollback clears that failed state; it does not release A’s lock or undo A’s pending update. Repeating work inside B’s failed transaction would only generate another transaction-state error.
Leave A open. B now gives the whole statement a shorter budget than the lock acquisition:
BEGIN;
SET LOCAL lock_timeout='100ms';
SET LOCAL statement_timeout='50ms';
\set ON_ERROR_STOP off
UPDATE timeout_demo.counter SET n=n+1 WHERE id=1;
\echo statement_state :SQLSTATE
\set ON_ERROR_STOP on
ROLLBACK;
This returns statement_state 57014. The operation is still waiting for the same row, but the statement deadline wins. These error classes are evidence to interpret with context: 55P03 can also arise from NOWAIT, and 57014 also covers other query cancellations. Neither code by itself proves a deadlock or names the blocking application.
In A, discard the pending value ten and release the row:
ROLLBACK;
B starts a fresh, bounded transaction and succeeds:
BEGIN;
SET LOCAL lock_timeout='100ms';
SET LOCAL statement_timeout='1s';
UPDATE timeout_demo.counter SET n=n+1 WHERE id=1 RETURNING n;
COMMIT;
The returned value is one. Neither failed B attempt committed an increment, and A rolled back its ten. Next, B demonstrates that ordinary elapsed execution does not consume the lock-wait budget:
BEGIN;
SET LOCAL lock_timeout='50ms';
SET LOCAL statement_timeout='1s';
SELECT pg_sleep(0.2);
\echo sleep_state :SQLSTATE
COMMIT;
SHOW lock_timeout;
The two-hundred-millisecond sleep succeeds with sleep_state 00000 despite a fifty-millisecond lock timeout. After commit, SHOW returns the session’s original zero because SET LOCAL ended with its transaction. Manually compare all printed SQLSTATE values, the restored timeout, and the final counter; the script does not assert them. A verifies the counter remains one and cleans up. On interruption, roll back both fixture transactions before removing their schema:
SELECT n FROM timeout_demo.counter WHERE id=1;
DROP SCHEMA timeout_demo CASCADE;
Recover without amplifying contention
Select retry policy per operation. A lock timeout may justify a new transaction after rollback, provided the request still has time and replay is valid. A small attempt cap, jitter, and an overall deadline bound the extra load; they cannot make a permanently blocked resource available. Keep the original error and report exhausted attempts instead of silently dropping the request.
A true deadlock is a cycle of dependencies. PostgreSQL detects it and reports 40P01; a lock timeout can fire before that detection, so a timed-out run does not establish that no cycle existed. Inspect blocker identities, transaction age, and lock acquisition order. Shortening a transaction that waits for external input often removes the cause more effectively than raising every caller’s timeout. Do not copy these laboratory millisecond limits into an unrelated workload without measuring normal hold times.
Sources
PostgreSQL 18 client connection defaults, lock, statement, and idle transaction timeout scopes, checked 2026-10-15.
PostgreSQL 18 explicit locking, conflicting locks and deadlock detection, checked 2026-10-15.
PostgreSQL error codes, SQLSTATE classes for lock unavailability, cancellation, and failed transactions, checked 2026-10-15.