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
- Make a check and the changes that depend on it all-or-nothing
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.
BEGIN is what PostgreSQL and MySQL accept; the standard spelling,
START TRANSACTION, works in both, and SQL Server writes
BEGIN TRANSACTION.
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 PostgreSQL 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.
PostgreSQL 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);BEGIN;UPDATE accounts SET balance = balance - 100 WHERE id = 1;UPDATE accounts SET balance = balance + 100 WHERE id = 2;ROLLBACK; SELECT owner, balance FROM accounts ORDER BY id;One statement is already atomic
A single UPDATE changes all the rows it matches or none of them, and
everything it reads — subqueries included — sees the data as it was when
the statement started. Split a check and two changes across separate
statements and each one sees what the previous one left behind — inside
a transaction or not:
CREATE TABLE accounts ( id int PRIMARY KEY, owner text NOT NULL, balance int NOT NULL);INSERT INTO accounts VALUES (1, 'Nino', 150), (2, 'Ana', 50);-- Move 100 from Nino to Ana, if Nino can afford it.UPDATE accounts SET balance = balance - 100WHERE id = 1 AND balance >= 100; UPDATE accounts SET balance = balance + 100WHERE id = 2 AND (SELECT balance FROM accounts WHERE id = 1) >= 100; SELECT owner, balance FROM accounts ORDER BY id;The first statement took 100 from Nino. By the time the second one checked his balance it was 50, so Ana was never credited and 100 simply vanished. Either check once, up front, inside a transaction and roll back if the check fails, or make the check and both changes one statement.
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.
Common mistakes
- Read, compute in the application, write back. Two requests both read
a balance of 300, both subtract 100 in code, both write 200: one
withdrawal is lost. Let the database do the arithmetic
(
SET balance = balance - 100), or lock the row you read withSELECT ... FOR UPDATE. - Assuming the default level freezes the data. Under
READ COMMITTEDeach statement takes a fresh snapshot, so two reads in one transaction can see different committed data. - Carrying on after an error. In PostgreSQL a failed statement aborts
the whole transaction; every later statement fails with "current
transaction is aborted, commands ignored until end of transaction
block" until you
ROLLBACK. Code that swallows the first error and continues gets a string of confusing failures instead.
What interviewers ask
- What does each letter of ACID guarantee? All-or-nothing; constraints hold at commit; concurrent transactions do not see each other's uncommitted work; committed data survives a crash.
- Name the read anomalies and the level that prevents each. Dirty read
(
READ COMMITTED), non-repeatable read (), phantom ( in the standard).
Exercise · Pro
A transfer that happens completely or not at all
Exercises are free in the first 3 lessons of every course. From here on they are part of Pro and Sprint; the lesson and its runnable examples stay open.
Upgrade