The Bank Transfer That Could Ruin Your Day
Imagine you transfer $100 from your savings account to your checking account. Behind the scenes, the database has to do two things:
1. Subtract $100 from your savings.
2. Add $100 to your checking.
Now imagine that right between step 1 and step 2, the server crashes. The $100 is gone from your savings. But it never made it to your checking. The bank just lost your money. You go to the ATM the next day and find your account light by $100.
This is the kind of disaster that ACID is designed to prevent.
ACID is a set of four properties that databases promise to enforce so that your data stays correct and reliable, even when things go wrong. The letters stand for:
Let us look at each one carefully, with the bank transfer running through the whole story.
Atomicity: All or Nothing
Atomicity is the simplest one to explain. It says: a transaction is one indivisible unit of work. Either every single step inside it happens, or none of them happen. There is no in-between.
Going back to the bank transfer. The transaction has two steps. With atomicity, the database guarantees one of two outcomes:
Outcome 1: Both steps succeed. $100 leaves savings, $100 lands in checking. Total money is preserved.
Outcome 2: Something fails partway through. The database undoes everything that happened so far. Your savings goes back to its original amount. Your checking is untouched. It is as if the transaction never happened.
The crucial thing: there is no third outcome where money disappears or appears. The database calls this "rolling back" the transaction.
from savings
to checking
from savings
before adding
Without atomicity, you would have to manually check the state of every operation and clean up after partial failures. With atomicity, the database does it for you.
Consistency: Rules Are Never Broken
Consistency means the database always moves from one valid state to another valid state. It never sits in an invalid state.
What is a "valid" state? It depends on the rules you have defined for your data. These rules are called constraints. Examples:
An account balance must never be negative.
An email column must contain a unique value.
A user ID must reference a real user that exists.
The total amount of money in the system must remain constant during a transfer.
If a transaction would break any of these rules, the database refuses to commit it and rolls it back.
Note: this consistency is different from the consistency in the CAP theorem (which is about distributed systems and replicas seeing the same data). ACID consistency is about preserving the rules of your data model.
The database does not invent these rules for you. You define them when you design your schema (constraints, foreign keys, check conditions). The database's job is to enforce them.
Isolation: Transactions Do Not Step on Each Other
In real systems, many transactions happen at the same time. Two people might be transferring money simultaneously. A million users might be reading and writing at once.
Isolation says: concurrent transactions should behave as if they ran one after another, not at the same time. Each transaction should not see the half-finished work of another transaction.
Without isolation, weird things happen. Let me show you the classic example.
The Race Condition Problem
Suppose your account has $100. You and your spouse both try to withdraw $80 at the same time, from different ATMs.
Both transactions read the same starting balance, both checked it independently, and both wrote their result. Neither knew the other was happening. The bank effectively gave away free money.
With isolation, the database makes one transaction wait until the other finishes. The second one would see the new balance ($20) and reject the withdrawal because $20 is less than $80.
Isolation Levels: The Trade-off
Perfect isolation is called serializable: the database behaves as if every transaction ran one at a time, in some order. This is the strictest guarantee. But it is also the slowest, because the database has to wait or lock things constantly.
In real life, databases offer different isolation levels. Each level prevents some bad behaviors but not others. The looser the isolation, the faster the system, but the more anomalies you might encounter.
| Isolation Level | Dirty Reads | Non-repeatable Reads | Phantom Reads | Performance |
|---|---|---|---|---|
| Read Uncommitted | Possible | Possible | Possible | Fastest |
| Read Committed | Prevented | Possible | Possible | Fast |
| Repeatable Read | Prevented | Prevented | Possible | Medium |
| Serializable | Prevented | Prevented | Prevented | Slowest |
Quick definitions for the anomalies in that table:
Dirty read: reading data from another transaction that has not been committed yet. The other transaction might still roll back, meaning you read something that "never happened."
Non-repeatable read: reading the same row twice in one transaction and getting different values, because another transaction updated it in between.
Phantom read: running the same query twice and getting different rows, because another transaction inserted or deleted rows that match your query.
Most production databases default to Read Committed (PostgreSQL, Oracle) or Repeatable Read (MySQL InnoDB). True serializable is rare in practice because of the performance cost.
Durability: Once Saved, Always Saved
Durability is the simplest property to state but the hardest to actually deliver. It says: once a transaction has been committed, the data must survive any failure.
If your database tells you "transfer complete" and then the server immediately loses power, the transfer must still be there when the server restarts. No "oops, we forgot." The data has to be on permanent storage before the database returns success.
Implementing durability is harder than it sounds. Disks are slow. If you wait for every single bit to be physically written before returning success, your database becomes painfully slow. So databases use clever tricks:
Write-ahead log (WAL): Before changing the actual data, write a small log entry describing the change. This log entry is fast to flush to disk. If the system crashes, on restart, the database reads the log and replays unfinished work.
fsync(): When the database wants to be sure data is on disk (not just in OS cache), it calls fsync(). This forces the OS to actually push the data to physical storage. Slow but guaranteed.
Replication: In distributed databases, durability often means writing to multiple machines. The transaction is not committed until enough replicas acknowledge they have stored the data. Now even if one whole machine fails, the data survives on the others.
How the Four Work Together
Each property addresses a different failure mode:
Atomicity protects against partial failures. Things either happen or they do not.
Consistency protects against bad data. The rules of your data are always honored.
Isolation protects against concurrent interference. Transactions do not see each other's mess.
Durability protects against losing committed work. Once it is in, it stays in.
Together, they let you reason about your database simply: every transaction either fully happens (preserving all the rules, untouched by others, permanently saved) or it never happened at all. There is no in-between.
This is an enormous simplification. Without ACID, every application would have to handle every kind of partial failure, race condition, and crash recovery on its own. With ACID, you write your business logic and the database deals with the messy parts.
ACID Is Not Free: The BASE Alternative
ACID guarantees come with a price. They require coordination, locking, logging, and synchronization. In a distributed system spanning many servers, achieving full ACID can be expensive or even impossible at scale.
That is why many modern systems, especially NoSQL databases, follow a different model called BASE:
BASE accepts that the system will be temporarily inconsistent in exchange for speed and availability. After a write, replicas will eventually agree, but not necessarily right away.
This is fine for things like social media feeds, shopping cart product views, or analytics. It is not fine for bank transfers, ticket booking, or anything where data correctness is non-negotiable.
When You Need ACID and When You Do Not
You absolutely need full ACID when:
Money is involved (banking, payments, accounting).
Inventory is finite and must not be oversold (ticket systems, e-commerce stock).
Multiple records must change together to remain valid (a transfer, an order with line items).
Regulations or audits require strict consistency (healthcare, finance, legal).
You can probably get away without full ACID when:
Data is mostly append-only (logs, events, metrics).
A few seconds of staleness does not matter (social posts, recommendations).
Eventual consistency is acceptable (search indexes, analytics).
You need extreme write throughput at the cost of consistency (real-time gaming, high-volume telemetry).
The One Thing to Remember
ACID is the contract that traditional databases sign with you. It says: hand me your data and your transactions, and I will keep things correct no matter what happens. Crashes, concurrency, hardware failures, broken constraints. None of that becomes your problem.
This contract is one of the great achievements of computer science. It lets you write business logic without thinking about every possible failure mode, and trust that the database will handle the rest.
Understanding ACID is not just trivia. It shapes how you design systems, choose databases, set isolation levels, and decide when to relax constraints for performance. Every architectural decision involving data eventually circles back to these four letters.