PostgreSQL's concurrency model is MVCC, in which each row version carries the inserting transaction's xmin and the deleting transaction's xmax. A transaction's snapshot decides which versions are visible, and old versions become garbage that VACUUM eventually reclaims. This model lets readers and writers proceed without blocking each other, but it does not eliminate lock waits when multiple transactions contend for the same row. Lock waits surface in pg_stat_activity under wait_event_type = 'Lock' and can be cross-referenced with pg_locks joined against pg_stat_activity for diagnosis.
When a query is blocked, the first line of defense is pg_cancel_backend(pid), which asks the backend to abort its current query; if that signal is ignored, the stronger pg_terminate_backend(pid) closes the entire connection. Two recurring causes of lock problems deserve attention. The first is deadlocks from transactions touching rows in different orders—a classic anti-pattern is updating two accounts by primary key in opposite directions; the fix is to always acquire locks in a stable order, typically by primary key. The second is idle-in-transaction sessions, in which a connection has opened a transaction and never committed or rolled back, holding row locks and blocking vacuum progress. The idle_in_transaction_session_timeout setting is a safety net: it kills such sessions after a configurable interval, and you can also hunt them proactively with SELECT pid, state, age(now(), xact_start) FROM pg_stat_activity WHERE state = 'idle in transaction'.
Isolation level affects which anomalies can occur. PostgreSQL defaults to READ COMMITTED, which is sufficient for most workloads. When stricter guarantees are needed, SET TRANSACTION ISOLATION LEVEL SERIALIZABLE prompts the database to detect serialization conflicts, but applications must be prepared to retry on the 40001 SQLSTATE. AUTOCOMMIT is on by default at the client driver level; every statement runs in its own transaction unless wrapped in BEGIN..COMMIT, which means accidental idle-in-transaction behavior usually traces back to drivers or middlewares holding implicit transactions across multiple statements. For bounding long-running statements broadly, statement_timeout can be set per role with ALTER ROLE app SET statement_timeout = '30s', with per-session overrides available as well.