Library
Learn/Databases/Chapter 03
7 min read

Transactions and ACID

Asha sends Ravi Rs 500. Inside the bank's database that is two changes: take 500 from Asha's balance, add 500 to Ravi's. Now imagine the server loses power after the first change and before the second. Asha is down 500, Ravi got nothing, and the money has simply left the universe.

A transaction is the database's promise that this cannot happen. A group of changes either all take effect or none of them do, even if the machine dies halfway through. It is the single most important thing a relational database gives you, and the reason banks, payments and bookings still run on one.

Your answer

The database confirms COMMIT, and one millisecond later the power goes out. When the server comes back, is the transfer there? How could the database possibly guarantee that?

A transaction in SQL#

sql
BEGIN;
UPDATE accounts SET balance = balance - 500 WHERE id = 'asha';
UPDATE accounts SET balance = balance + 500 WHERE id = 'ravi';
COMMIT;

Between BEGIN and COMMIT, nothing is final. If anything fails, a constraint, a crash, or your code calling ROLLBACK, every change since BEGIN is undone as if it never happened.

ACID, in plain words#

The four letters are the four promises a transaction makes.

Atomic. All or nothing. Both updates happen, or neither does. There is no state where only one did.

Consistent. A transaction moves the database from one valid state to another. Rules you declared, such as CHECK (balance >= 0), a foreign key, or a unique email, are never broken by a committed transaction. If one would be, the whole transaction is rejected.

Isolated. Transactions running at the same time do not see each other's half finished work. How strictly this holds is adjustable, and getting it wrong causes real bugs, so it has its own chapter.

Durable. Once the database says COMMIT succeeded, the change survives a crash, a power cut or a restart.

How the database keeps the promise#

The trick behind atomic and durable is the write ahead log, or WAL. Before the database changes the real table pages, it first appends a record of the change to a log file on disk. Appending to the end of a file is fast, and it is the log, not the tables, that has to be safe before COMMIT returns. Step through a transfer that gets interrupted.

1/7 The change is made to the page in memory, and a log record describing it is created. Nothing on disk is final yet.

So the answer to the question at the top: yes, the transfer survives. COMMIT does not return until the log is safely on disk, and the log is enough to rebuild everything else. The table pages are written to disk later, in the background, in big efficient batches.

Durability is a setting

Some systems let you trade durability for speed: Postgres has synchronous_commit = off, MySQL has innodb_flush_log_at_trx_commit set to something other than 1, and many managed services expose similar knobs. With them, COMMIT can return before the log is on disk, and a crash can lose the last fraction of a second of committed work. Fine for analytics events. Not fine for money. Know what yours is set to.

Keep transactions short#

A transaction holds locks on the rows it changed until it ends, so other transactions that want those rows wait. The longer it stays open, the more everyone else queues behind it.

The most common mistake: calling something slow inside a transaction.

text
BEGIN
  insert order
  call payment gateway     <- 3 seconds, sometimes 30
  update order status
COMMIT

For those seconds, the order rows stay locked, a database connection is held, and if the gateway times out you are left deciding whether the payment happened. Do the external call outside the transaction, record its result in a short transaction of its own, and design the external call to be safe to retry (an idempotency key), the way Design a UPI style payment rail does.

When one database is not enough#

A transaction protects changes inside one database. The moment a business action spans two services, say the order service and the wallet service, each with its own database, there is no single BEGIN and COMMIT covering both. The usual answer is a saga: a series of local transactions, each with a compensating action that undoes it (refund the wallet if the order fails), coordinated through events. It is more work and weaker than a real transaction, which is a good reason to keep data that must change together in the same database.

Which promise was broken?
A crash leaves Asha debited and Ravi not credited
A balance becomes -200 despite a CHECK (balance >= 0) rule
A report reads Asha's debit before the credit to Ravi commits
COMMIT returned success, then a power cut erased the transfer

Checkpoint#

Checkpoint

1. How does a database make a committed transaction survive a crash?

2. Why is calling a payment gateway inside a database transaction a bad idea?

3. An order service and a wallet service each have their own database. How do you keep them in step?

Worth memorising
  • ACID: all or nothing, rules never broken, no peeking at unfinished work, committed means saved.
  • The write ahead log makes COMMIT durable: log on disk first, tables later.
  • Keep transactions short. No network calls inside.
  • Across services there is no transaction. Use a saga with compensations.
Say this in 60 seconds

A transaction groups changes so they all happen or none do. ACID means atomic, all or nothing; consistent, declared rules like constraints are never broken; isolated, concurrent transactions do not see each other's unfinished work; and durable, a commit survives a crash. Atomicity and durability come from the write ahead log: changes are appended to a log that is flushed to disk before COMMIT returns, and on restart the database redoes committed work and undoes the rest. I keep transactions short, with no external calls inside, because they hold locks. And when an action spans two services, I use a saga with compensating steps, since one transaction cannot cover two databases.