Group several statements into one all-or-nothing unit with BEGIN, COMMIT and ROLLBACK, undo part of the work with SAVEPOINT, and control how concurrent transactions see each other with isolation levels.
Why: a transaction bundles several statements so they all succeed together or none of them happen. BEGIN starts it, COMMIT makes every change permanent, and ROLLBACK throws them all away. The classic example is moving money: both the withdrawal and the deposit must happen, or neither should.
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT; -- both updates land together; ROLLBACK; would cancel bothWhy: without a transaction, if the server crashed between those two updates, money would vanish — debited from one account but never credited to the other. The transaction guarantees the database is never left in that half-finished state.
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- something goes wrong here...
ROLLBACK; -- the balance is restored as if nothing happenedWhy: a SAVEPOINT is a checkpoint inside a transaction. You can roll back to it to undo just the most recent part of the work while keeping everything before it, then carry on toward COMMIT.
BEGIN;
INSERT INTO orders (user_id, total) VALUES (1, 50);
SAVEPOINT before_item;
INSERT INTO order_items (order_id, product_id, quantity) VALUES (1, 999, 1);
-- product 999 was wrong:
ROLLBACK TO SAVEPOINT before_item;
INSERT INTO order_items (order_id, product_id, quantity) VALUES (1, 42, 1);
COMMIT;Why: when many transactions run at once, the isolation level decides how much they can see of each other’s uncommitted work. PostgreSQL defaults to READ COMMITTED (you only see committed data). Raise it to REPEATABLE READ or SERIALIZABLE when a transaction must see a perfectly stable snapshot from start to finish.
BEGIN ISOLATION LEVEL REPEATABLE READ;
-- every query in here sees the same frozen snapshot of the data,
-- even if other transactions commit changes meanwhile
SELECT SUM(balance) FROM accounts;
COMMIT;Why: the guarantees above have a name — ACID. Atomicity: every statement succeeds or none do (BEGIN/COMMIT/ROLLBACK). Consistency: a transaction moves the database from one valid state to another, never breaking your constraints. Isolation: concurrent transactions do not step on each other (the isolation levels above). Durability: once COMMIT returns, the data survives a crash. You do not switch ACID on — it is what every PostgreSQL transaction already provides.
BEGIN; -- Atomicity: all-or-nothing
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
-- Consistency: a CHECK (balance >= 0) constraint would abort the
-- transaction here if either update left an invalid row.
-- Isolation: other sessions can't see these changes until COMMIT.
COMMIT; -- Durability: survives a crash now