After this lesson you can
- Wrap several statements so they succeed or fail together
- Name what each of the four ACID letters guarantees
- Explain the anomalies the isolation levels rule out
A transaction groups statements so the database applies all of them or none of them.
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;
If the second update fails, ROLLBACK leaves the first as if it never
happened. Money does not evaporate between two statements.
ACID, in one line each
- Atomic — the whole transaction applies, or none of it does.
- Consistent — constraints hold before and after; a transaction cannot leave the database in a state the schema forbids.
- Isolated — concurrent transactions do not see each other's half-finished work.
- Durable — once
COMMITreturns, the change survives the power going out.
What isolation is actually for
Isolation levels are defined by the anomalies they forbid:
- Dirty read — you see another transaction's uncommitted change. Every
level above
READ UNCOMMITTEDforbids this, and Postgres never allows it. - Non-repeatable read — you read a row twice in one transaction and get
two different values, because somebody committed in between.
REPEATABLE READforbids it. - Phantom read — you run the same
WHEREtwice and the second run has extra rows.SERIALIZABLEforbids it.
Postgres defaults to READ COMMITTED: each statement sees a snapshot taken
when that statement started. Two statements in the same transaction can
therefore disagree, which surprises people who assumed the transaction froze
the world.
Try it
CREATE TABLE accounts ( id int PRIMARY KEY, owner text NOT NULL, balance int NOT NULL CHECK (balance >= 0));INSERT INTO accounts VALUES (1, 'Nino', 300), (2, 'Ana', 50);-- The CHECK constraint refuses to let Nino go below zero, so this-- transfer of 1000 cannot complete. Nothing is half-applied.SELECT owner, balance FROM accounts ORDER BY id;The practical warning
A transaction holds locks and a database connection for as long as it is open. Doing anything slow inside one — an HTTP call to a payment provider, a large file write, waiting on a user — holds that connection while it happens, and a pool of twenty connections drains in seconds under load. Keep transactions short and keep network calls outside them.
Try it yourself
2 visible tests · 2 hidden testsTable accounts(id, owner, balance). A transfer is only safe to commit
if it never leaves a balance negative — that is exactly what a CHECK
constraint or a transaction's own validation would enforce. Return the
owner of every account that is already negative, so you would know
before touching a single row that something upstream broke isolation.
Order by owner.
(Grading here can only run a read: the sandbox executes every submission
inside a read-only transaction, so this task is a SELECT, not a
transfer. What it checks — a correct filter and a correct order — is
real; the framing is what a pre-transfer safety check looks like.)
CREATE TABLE accounts ( id int PRIMARY KEY, owner text NOT NULL, balance int NOT NULL);INSERT INTO accounts VALUES (1, 'Nino', 300), (2, 'Ana', -50), (3, 'Luka', 0), (4, 'Mari', -1);Sign up to check the hidden tests and save your progress. Sign up