49. Transactions and ACID
Why transactions exist: the multi-statement problem
Every write so far in this course has been a single statement. Real
operations are often multiple statements that must succeed or fail
together — the canonical example: transferring money between two
accounts is a debit and a credit, two separate UPDATEs. If the debit
succeeds and the credit fails (a crash, a constraint violation, anything),
you cannot leave the database in that half-done state — money would
simply vanish. A transaction is how SQL guarantees "all of these
statements happen, or none of them do."
ACID, precisely
Four guarantees, each solving a specific failure mode:
- Atomicity — a transaction's statements are treated as one indivisible unit. Either every statement's effects persist, or none do — no partial application, even across a crash mid-transaction.
- Consistency — a transaction moves the database from one valid state to another, never violating a constraint (Module 2) along the way. This is really a consequence of the other three properties plus your constraints being correctly defined, more than an independent mechanism.
- Isolation — concurrent transactions don't see each other's uncommitted, in-progress changes (the full subject of the next lesson — isolation has levels, not just an on/off state).
- Durability — once a transaction commits, its effects survive even a subsequent crash (written to durable storage, not just memory).
BEGIN, COMMIT, ROLLBACK — the mechanism
BEGIN;
UPDATE customer SET email = 'new@example.com' WHERE customer_id = 1;
UPDATE customer SET email = 'other@example.com' WHERE customer_id = 2;
COMMIT; -- both updates become permanent together
Or, to abandon the whole thing:
BEGIN;
UPDATE customer SET email = 'mistake@example.com' WHERE customer_id = 1;
ROLLBACK; -- undoes the update entirely, as if it never happened
Every single statement outside an explicit BEGIN runs in its own
implicit one-statement transaction, auto-committed immediately — this
is why every UPDATE/INSERT/DELETE you've run casually throughout
this course "just worked" without you ever typing BEGIN/COMMIT.
Explicit transactions matter specifically when you need multiple
statements to succeed or fail as one unit.
What "seeing" an uncommitted change means
Within the same session, your own uncommitted changes are visible to your own subsequent statements immediately — this isn't isolation being violated, it's just normal sequential execution:
BEGIN;
UPDATE customer SET email = 'temp@example.com' WHERE customer_id = 1;
SELECT email FROM customer WHERE customer_id = 1; -- shows 'temp@example.com' already
ROLLBACK;
SELECT email FROM customer WHERE customer_id = 1; -- back to the original, unchanged
Whether a different session/connection can see your uncommitted change
before you COMMIT is the actual subject of isolation (next lesson) —
under every standard isolation level, the answer is no, other sessions
never see uncommitted data, full stop.
SAVEPOINT: a checkpoint within a transaction
A SAVEPOINT lets you roll back part of a transaction without
abandoning the whole thing — useful when a multi-step transaction has
one risky step you want to be able to retry or skip without losing the
steps before it:
BEGIN;
UPDATE customer SET email = 'ok@example.com' WHERE customer_id = 1;
SAVEPOINT before_risky_step;
UPDATE customer SET email = 'oops@example.com' WHERE customer_id = 999999; -- matches nothing, but imagine it errored
ROLLBACK TO SAVEPOINT before_risky_step; -- undo just this step, keep the transaction open
UPDATE customer SET email = 'other@example.com' WHERE customer_id = 2;
COMMIT; -- customer 1 and customer 2 updates persist; the "risky" attempt does not
ROLLBACK TO SAVEPOINT undoes everything back to that named point, but
keeps the transaction itself open — you can keep working, and
eventually COMMIT or fully ROLLBACK the whole thing. This is
distinct from a plain ROLLBACK, which ends the transaction entirely.
A critical, real gotcha: errors abort the whole transaction
In Postgres, once any statement inside a transaction errors, every
subsequent statement in that transaction is rejected — even valid ones
— until you either ROLLBACK (the whole transaction) or ROLLBACK TO
a SAVEPOINT taken before the error:
BEGIN;
UPDATE customer SET email = 'fine@example.com' WHERE customer_id = 1;
SELECT 1/0; -- an error — division by zero
UPDATE customer SET email = 'also-fine@example.com' WHERE customer_id = 2;
-- ERROR: current transaction is aborted, commands ignored until end of transaction block
COMMIT; -- this COMMIT does nothing useful — the transaction is already dead, effectively a ROLLBACK
This is a genuinely common source of confusion for people used to other
databases (some engines let you continue a transaction after a
non-fatal error) — in Postgres, wrap any step you expect might fail
in its own SAVEPOINT if you want to recover from it and keep going,
rather than assuming the transaction survives an error on its own.
Check yourself
- What does "Atomicity" guarantee, specifically, that a sequence of auto-committed individual statements does not?
- What's the difference between
ROLLBACKandROLLBACK TO SAVEPOINT name? - A transaction has an error on its third statement, and you don't
ROLLBACKor use aSAVEPOINT. What happens to a fourth, otherwise valid statement in that same transaction?