Skip to content

55P03 — canceling statement due to lock timeout ​

PostgreSQL raises 55P03 on a lock_timeout expiry or a conflicting NOWAIT row-lock request; an error inside BEGIN leaves a failed transaction block until rollback or valid savepoint recovery.

Reproduce it ​

Two sessions want the same row. B caps how long it's willing to queue. The -- [A] and -- [B] markers say who runs each statement, top to bottom.

sql
-- [A] lock the row and hold it
BEGIN;
UPDATE accounts SET balance = 200 WHERE id = 1;

-- [B] wait at most 100ms for that lock, then give up
SET lock_timeout = '100ms';
UPDATE accounts SET balance = 300 WHERE id = 1;

B's standalone UPDATE fails with 55P03; the implicit transaction ends. The linked full Scenario then observes a successful UPDATE and balance 300 after A commits. It does not execute a failed-BEGIN branch. The PostgreSQL 18 manual documents full rollback or valid-savepoint recovery from an "aborted state by the system due to an error". The separate statement-error Scenario asserts 25P02 and full rollback after division by zero; savepoints have their own recovery demonstration. lock_timeout applies per lock acquisition, including table locks; NOWAIT applies to the requested row locks and does not remove table-lock waits. No exact cancellation latency is asserted.

Run it yourself ​

The NOWAIT, lock_timeout, SKIP LOCKED lesson renders this scenario as a verified transcript and lines up all three ways to refuse an open-ended wait. For the mechanics of the queue B is opting out of, read Lock queues and Row locks.

The MySQL side ​

InnoDB row-lock timeout uses 1205, while NOWAIT uses 3572. Timeout rollback depends on innodb_rollback_on_timeout; metadata waits have a separate lock_wait_timeout. These codes do not imply identical transaction recovery across engines.

MIT Licensed · Every transcript on this site was generated by a real database run against MySQL 8.4.11 and PostgreSQL 18.6 at 8fa27b8; shared YAML has an independent psycopg/PyMySQL check.