Skip to content

Transactions & Concurrency

So far every statement you wrote ran on its own. In real applications, though, a single business action often needs several statements that must all succeed together or not at all. Moving money between two accounts is the classic case: one row goes down, another goes up, and you can never afford to apply only half of that.

A transaction is the tool for exactly this. It groups one or more statements into a single all-or-nothing unit of work. Either every statement takes effect, or none of them do. On top of that, a busy database has many transactions running at the same time, which is what we mean by concurrency. This module is about both: how to define units of work, and how PostgreSQL keeps them from corrupting each other’s data.

A transaction in PostgreSQL gives you four guarantees, remembered by the acronym ACID.

LetterPropertyWhat it promises
AAtomicityAll statements commit together or roll back together; never half-done
CConsistencyThe database moves from one valid state to another, honouring constraints
IIsolationConcurrent transactions do not see each other’s unfinished work
DDurabilityOnce committed, the change survives a crash or power loss

Atomicity is the part you feel most directly when you type COMMIT or ROLLBACK. Isolation is the part that gets interesting once more than one client is connected, and it is where most of this module’s depth lies.

The next four lessons build on each other. You start a transaction, you choose how isolated it should be, and underneath both of those PostgreSQL uses two mechanisms to make concurrency safe: keeping multiple versions of each row, and taking locks when it must.

flowchart TD
  A[Transaction: BEGIN to COMMIT] --> B[Isolation level: how much it sees]
  B --> C[MVCC: each transaction reads a snapshot]
  B --> D[Locking: serialize conflicting writes]
  C --> E[Safe concurrency for many clients]
  D --> E
From a single transaction to safe concurrency
  • Transaction basicsBEGIN, COMMIT, ROLLBACK, atomicity, and SAVEPOINT for partial rollbacks, demonstrated with an account transfer.
  • Isolation levels — Read Committed, Repeatable Read, and Serializable, and the read anomalies each one prevents.
  • MVCC — Multi-Version Concurrency Control, where writers do not block readers and readers do not block writers because each transaction reads a consistent snapshot.
  • Locking and deadlocks — explicit row locks with SELECT ... FOR UPDATE, why two transactions can deadlock, and how PostgreSQL breaks the tie.

You will meet these commands properly in the next lesson, but here is the shape of a transaction so the rest of the module reads naturally.

BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;

In pgAdmin: the Query Tool runs each statement in its own auto-committed transaction by default. To group statements, type BEGIN; yourself at the top and COMMIT; at the end, then run the whole block at once.

  • A connection that is not inside an explicit transaction runs each statement in its own implicit transaction, committed immediately. This is called auto-commit.
  • Isolation is a spectrum, not a switch. Higher levels prevent more anomalies but make conflicts more likely to abort.
  • PostgreSQL never asks readers to wait for writers for ordinary SELECT queries; that is the headline benefit of MVCC.
  • Deadlocks are a normal possibility in any concurrent system, not a bug; well-written applications expect them and retry.
What does the "A" in ACID guarantee about a transaction?
What does "concurrency" mean in the context of a database?
Which ACID property describes that concurrent transactions do not see each other unfinished work?