Transactions
A transaction is a group of SQL statements that must all succeed or all fail together. Transactions are the backbone of data integrity in every production database — from bank transfers to order placements.
ACID Properties
The four guarantees every transaction must uphold
The Four Properties
ACID is not just theory — it's what makes PostgreSQL safe for money, orders, and any critical data. Every transaction in PostgreSQL guarantees all four.
ACID in Action — Bank Transfer Example
A money transfer is the classic ACID example. Debit one account, credit another — both must happen or neither should.
UPDATE accounts SET balance = balance - 500 WHERE id = 1; -- 💥 server crashes here! UPDATE accounts SET balance = balance + 500 WHERE id = 2; -- ← never runs -- Alice lost ₹500, Bob got nothing
BEGIN; UPDATE accounts SET balance = balance - 500 WHERE id = 1; UPDATE accounts SET balance = balance + 500 WHERE id = 2; COMMIT; -- Both happen, or neither do ✅
Transaction Control
BEGIN · COMMIT · ROLLBACK · SAVEPOINT
BEGIN
BEGIN (or START TRANSACTION) opens a transaction block. Everything after it is part of the same transaction until you COMMIT or ROLLBACK.
INSERT INTO ... without a BEGIN is already a transaction of one.
COMMIT
COMMIT makes all changes in the transaction permanent. After COMMIT, other sessions can see the new data, and the changes survive a crash.
ROLLBACK
ROLLBACK undoes everything done since BEGIN. The database goes back to exactly the state it was in before the transaction started.
SAVEPOINT
A SAVEPOINT is a checkpoint inside a transaction. You can roll back to a savepoint without abandoning the entire transaction. Think of it as a partial undo.
Interactive — E-commerce Order Transaction
Simulate an order placement transaction step by step. Click each SQL statement in order.
🛒 Place Order: User buys 2x Product #7 (stock: 10)
order_items: []
inventory #7: 10
payments: []
─────────────────
txn status: none
Isolation Levels
How much one transaction sees of another's in-progress work
What is Isolation?
In a real system, hundreds of transactions run at the same time. Isolation determines what each transaction can see from other concurrent transactions. More isolation = safer but slower. Less isolation = faster but riskier.
| Isolation Level | Dirty Read | Non-repeatable Read | Phantom Read | Performance |
|---|---|---|---|---|
| Read Uncommitted | Possible | Possible | Possible | Fastest |
| Read Committed ← PG default | Safe | Possible | Possible | Fast |
| Repeatable Read | Safe | Safe | PG: Safe* | Medium |
| Serializable | Safe | Safe | Safe | Slowest |
* PostgreSQL's REPEATABLE READ uses snapshot isolation which also prevents phantom reads — stronger than the SQL standard requires.
Setting Isolation Levels
Explore Each Isolation Level
Click each level to see what it means, what problems it prevents, and when to use it in production.
Concurrency Problems
Dirty reads · Non-repeatable reads · Phantom reads
Dirty Read
Transaction A reads data that Transaction B has written but not yet committed. If B rolls back, A has read data that never officially existed.
BEGIN; -- reads balance = 5000 -- (Bob's tx not committed yet) SELECT balance FROM accounts WHERE id=1; -- sees: 5000 ← DIRTY READ ❌ -- Bob's tx rolls back! -- This 5000 never existed.
BEGIN; UPDATE accounts SET balance = 5000 WHERE id = 1; -- not committed yet! ROLLBACK; -- ← balance is back to original
Non-Repeatable Read
Transaction A reads the same row twice in one transaction and gets different values because Transaction B committed a change in between.
BEGIN; SELECT price FROM products WHERE id = 42; -- reads: ₹1000 -- (B commits its update here) SELECT price FROM products WHERE id = 42; -- reads: ₹1200 ← DIFFERENT! ❌ -- Same query, same row, -- different result.
BEGIN; UPDATE products SET price = 1200 WHERE id = 42; COMMIT; -- ← A's second read -- sees this change
REPEATABLE READ isolation. A's snapshot is frozen at the start — both reads return ₹1000 regardless of B.Phantom Read
Transaction A runs the same query twice and gets different rows because Transaction B inserted (or deleted) rows that match A's WHERE clause.
BEGIN; SELECT COUNT(*) FROM orders WHERE status = 'pending'; -- returns: 5 -- (B inserts and commits here) SELECT COUNT(*) FROM orders WHERE status = 'pending'; -- returns: 6 ← PHANTOM ❌ -- A new "phantom" row appeared
BEGIN; INSERT INTO orders (user_id, status) VALUES (99, 'pending'); COMMIT; -- ← A's second COUNT -- includes this row
REPEATABLE READ or SERIALIZABLE. PostgreSQL's snapshot isolation prevents phantom reads at REPEATABLE READ level — better than the SQL standard requires.Summary — Which Problem, Which Fix
| Problem | What happens | PostgreSQL default? | Fix with |
|---|---|---|---|
| Dirty Read | Read uncommitted data that may roll back | Already prevented | Read Committed (default) |
| Non-repeatable Read | Same row returns different values in same txn | Can happen | REPEATABLE READ |
| Phantom Read | Same query returns different row counts in same txn | Can happen at RC | REPEATABLE READ or SERIALIZABLE |
Real-World Example: E-commerce Order Placement
A production-grade transaction with error handling
Complete Order Placement Transaction
This is how Swiggy, Amazon, and Flipkart handle order placement — all steps in one atomic transaction.
EXCEPTION handler that does the ROLLBACK automatically. Your Python/Node.js app calls the function — one round-trip, fully atomic.
Wrapped in a PL/pgSQL Function (production style)
Quick Reference Cheat Sheet
Everything at a glance
| Command | Effect | When |
|---|---|---|
BEGIN | Open transaction | Before any grouped statements |
COMMIT | Persist all changes | All steps succeeded |
ROLLBACK | Undo everything | Any step failed |
SAVEPOINT | Mark a partial checkpoint | Before risky sub-operations |
ROLLBACK TO | Undo back to savepoint | Sub-operation failed |