BEGIN, COMMIT, ROLLBACK, isolation levels, and safe atomic operations — what every production application must understand.
P-3 — Transactions and ACID in Practice
Who this module is for: You can write queries and design schemas. Now you need to understand how to group multiple operations into an atomic unit — so that either all of them succeed or none of them do. This is the mechanism that prevents your application from leaving the database in a half-updated, inconsistent state.
The Problem Transactions Solve
Imagine a bank transfer: debit $100 from Alice, credit $100 to Bob.
UPDATE accounts SET balance = balance - 100 WHERE id = 1; -- Alice
UPDATE accounts SET balance = balance + 100 WHERE id = 2; -- Bob
What happens if the server crashes, the network drops, or an error occurs between the two statements? Alice has lost $100 and Bob never received it. The money has vanished.
Transactions prevent this by grouping operations: either both updates happen, or neither does.
BEGIN, COMMIT, ROLLBACK
BEGIN; -- start a transaction
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT; -- make both changes permanent
If anything goes wrong between BEGIN and COMMIT:
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- Something goes wrong (application error, network failure, etc.)
ROLLBACK; -- undo the debit — Alice gets her $100 back
Without a BEGIN, every statement is its own transaction — it auto-commits immediately. This is fine for isolated statements, but dangerous for multi-step operations.
With a BEGIN, nothing is visible to other connections until COMMIT. If the session disconnects before COMMIT, PostgreSQL automatically rolls back the transaction.
ACID: What It Actually Means
A — Atomicity: All operations in a transaction succeed, or none do. There is no partial success.
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- Crash here → the UPDATE is rolled back. Alice keeps her $100.
C — Consistency: A transaction brings the database from one valid state to another. Constraints (NOT NULL, FOREIGN KEY, CHECK) are enforced at commit time.
BEGIN;
UPDATE accounts SET balance = balance - 1000 WHERE id = 1; -- balance would go to -500
-- CHECK (balance >= 0) fires at COMMIT
COMMIT; -- ERROR: check constraint violated → automatic rollback
I — Isolation: Transactions run as if they are the only transaction in the system. Other transactions' uncommitted changes are invisible.
-- Session A:
BEGIN;
UPDATE tasks SET status = 'in_progress' WHERE id = 1;
-- NOT committed yet
-- Session B (concurrent):
SELECT status FROM tasks WHERE id = 1;
-- Returns 'todo' — Session A's change is not visible until committed
D — Durability: Once committed, changes survive crashes. PostgreSQL's Write-Ahead Log (WAL) ensures this.
Isolation Levels in Practice
The SQL standard defines four isolation levels; PostgreSQL accepts all four names but only implements three distinct behaviors — READ UNCOMMITTED is accepted syntactically and silently mapped to READ COMMITTED (Postgres never does dirty reads). The default, and the one you will use for almost everything, is READ COMMITTED.
READ COMMITTED (default)
Each query in a transaction sees a fresh snapshot of committed data at the time that query starts. This means a transaction can see different data at the beginning vs. the end if other transactions commit in between.
-- Session A:
BEGIN;
SELECT COUNT(*) FROM tasks; -- returns 100
-- Session B (commits a new task while A is open):
INSERT INTO tasks (...) VALUES (...);
COMMIT;
-- Session A continues:
SELECT COUNT(*) FROM tasks; -- returns 101 — sees B's committed change
COMMIT;
This is acceptable for most application reads. Each query gets a consistent view; the transaction as a whole does not.
REPEATABLE READ
The entire transaction sees the same snapshot of data as of when the transaction started. New inserts/updates by other transactions are invisible for the duration.
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN;
SELECT COUNT(*) FROM tasks; -- returns 100
-- Session B inserts and commits a task while we wait
SELECT COUNT(*) FROM tasks; -- still returns 100 — snapshot is frozen
COMMIT;
Use this when your transaction needs a consistent view across multiple queries — for example, generating a report where all queries must see the same data point in time.
SERIALIZABLE
The strongest level. Transactions execute as if they were serialised one after another. PostgreSQL detects situations where concurrent transactions could produce results different from any serial execution and fails one of them.
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
-- Used for financial operations where concurrent updates to related rows
-- could produce incorrect results even if each individual update is correct
Use this sparingly — it adds overhead and can cause transaction failures that need retrying.
Rule for most applications: use the default READ COMMITTED. Move to REPEATABLE READ only when you need a consistent multi-query snapshot. Use SERIALIZABLE only for complex financial operations where non-serialisable anomalies are a real risk.
SAVEPOINT — Partial Rollback
Sign in to keep reading
The rest of this module is free — sign in with Google to unlock it and track your progress.
Sign in & RegisterDiscussion
0Join the discussion