Build the mental model
PostgreSQL takes implicit locks to preserve consistency. `SELECT ... FOR UPDATE` can protect a read-then-write decision, but lock duration should remain short. If two transactions lock resources in opposite order, a deadlock can form; PostgreSQL aborts one participant to break the cycle.
Connect it to a real scenario
For seat inventory, lock the row before decrementing availability. Across the application, acquire rows in a consistent order such as ascending ID, set sensible timeouts, and retry aborted transactions.
Try the working example
BEGIN;
SET LOCAL lock_timeout = '2s';
SELECT seats_left
FROM app.course_sessions
WHERE session_id = 7
FOR UPDATE;
UPDATE app.course_sessions
SET seats_left = seats_left - 1
WHERE session_id = 7 AND seats_left > 0
RETURNING seats_left;
COMMIT;Concurrent bookings serialize the update and avoid negative inventory.5-minute try-it
In two terminals, lock two rows in opposite order to trigger and observe a deadlock.
One important caution
Treating a lock wait as a generic application hang without locating the blocker prolongs incidents.
PostgreSQL — Explicit Locking — PostgreSQL Global Development Group