ACID transactions simulator
ACID is the set of promises a database makes about transactions: all or nothing, always valid, not disturbed by others, and permanent once committed. Below, a bank transfer runs through a small database with memory, a log and a disk. Pick a property, switch its safeguard off, and see what breaks. Further down: each property with examples and how real databases provide it.
- 1T1 beginon disk
- 2T1 update A: 100 → 70on disk
After recovery A = 100, B = 100: the half-finished transfer left no trace. All or nothing.
- 8Undo pass: transaction 1 never committed, so its changes are rolled back using the before-images in the log: A = 100, B = 100.
- 7Restart, redo pass: replay all 1 logged update in order, which brings disk to A = 70, B = 100.
- 6Power failure. Everything in memory is gone: the buffer pool, though every log record had already reached disk. Disk now holds A = 70, B = 100.
- 5The buffer pool needs room and writes page A = 70 to disk, though its transaction has not committed.
- 4Write-ahead rule: before page A goes to disk, the log records up to LSN 2 must be on disk. 2 records are flushed first.
- 3Transaction 1 sets A = 70 in memory, and appends a log record (LSN 2: A 100 → 70).
- 2Transaction 1 reads A = 100 and locks the row.
- 1Transaction 1 begins.
How to use the simulator
The database holds two accounts, A and B, starting at 100 each, so the total is always 200 unless something goes wrong. It has three tiers: the buffer pool in memory, where pages are read and changed; the write-ahead log, a list of change records that are first kept in memory and then flushed to disk; and the data pages on disk. A crash erases memory and leaves the disk as it was.
Choose a property. Each runs a short script, a transfer or a pair of deposits, sometimes with a crash in it. Then switch the safeguard off and run it again:
- Atomicity: with the log, recovery undoes the half-finished transfer; without it, 30 disappears.
- Consistency: with a CHECK constraint the overdraft is refused; without it, A goes to −30.
- Isolation: with row locks the second deposit waits and both count; without them one is lost.
- Durability: if the log is flushed at commit, the crash cannot undo the transfer; if commit does not wait for the disk, a "committed" transfer vanishes.
This is a teaching model in the style of ARIES, the recovery method many databases build on, much simplified: two rows, one page each, and a crash only where the script puts it. Real engines add checkpoints, compensation log records, group commit and much more, but the rules shown here are the ones they follow.
What is ACID?
A transaction is a group of reads and writes that a database treats as one unit, such as "subtract 30 from account A and add 30 to account B". ACID names the four guarantees a database gives for transactions:
- Atomicity: all of the transaction's changes happen, or none do.
- Consistency: the transaction takes the database from one valid state to another; rules such as constraints are never broken.
- Isolation: transactions running at the same time do not interfere; each behaves as if it ran alone (to a degree set by the isolation level).
- Durability: once the database says a transaction committed, it stays committed, even after a crash or power cut.
Together they let application code be written as if nothing ever failed and nobody else used the database at the same time, which is the reason transactions exist.
Atomicity
Atomicity means a transaction cannot stop halfway. If anything goes wrong before it commits, whether an error, a constraint violation, a deadlock, a client disconnecting or a crash, every change it made is undone, and the database looks as if the transaction never started. The application can then simply retry.
The hard case is a crash. To work fast, databases let changed pages sit in memory and write them to disk later, and they may even write a page changed by a transaction that has not committed, if memory is needed for something else (the simulator's atomicity script does exactly that). So after a crash, the disk can hold half a transaction. The write-ahead log fixes this: every change is logged with its old and new value before the changed page may be written, so recovery can find the unfinished transaction and put the old values back. Despite the name, atomicity is not about concurrency; that is isolation.
Consistency
Consistency means the data always obeys its rules. Some rules the database enforces itself: NOT NULL, UNIQUE, foreign keys, and CHECK constraints such as CHECK (balance >= 0). A statement that would break one fails, and the transaction can be rolled back as a whole. Other rules live only in the application, such as "the total of all accounts never changes in a transfer"; the database keeps them only if every transaction is written correctly.
For that reason, many authors, including Martin Kleppmann in Designing Data-Intensive Applications, point out that the C is different from the other three: it is a property the application achieves using atomicity, isolation and durability, plus whatever constraints it declares. Declaring constraints in the database, rather than only checking in code, is what turns a rule into a guarantee.
Isolation
Isolation means concurrent transactions do not see or overwrite each other's unfinished work. The strongest form, serializability, guarantees the result is the same as running them one at a time. In the simulator's isolation script, two deposits read the same balance at the same time; without locking, the second one writes back a value computed from a stale read and the first deposit is lost.
Databases provide isolation with locks (a transaction waits for rows another transaction is changing) or with multi-version concurrency control (each transaction reads a consistent snapshot, and conflicting writes are blocked or rejected). Full serializability costs throughput, so databases offer weaker isolation levels, and most run at read committed by default, which does not prevent every anomaly. The isolation levels simulator shows each level against each anomaly, checked on PostgreSQL.
Durability
Durability means a committed transaction survives crashes. The database does not need to write the changed data pages to disk at commit, which would be slow, because they could be anywhere on the disk. It only needs to make sure the transaction's log records are on disk: they are appended in one place, so writing them is fast, and they are enough to redo the changes after a crash. The commit is reported to the client only after the operating system confirms, with fsync or similar, that the log has reached stable storage.
Durability against a single machine failing outright, a dead disk or a lost data centre, needs copies elsewhere: replicas, ideally with synchronous replication for data that must not be lost, and backups.
What goes wrong without each guarantee
The simulator's four scripts, with each safeguard on and off:
| Property | Scenario | With the safeguard | Without it |
|---|---|---|---|
| Atomicity With a write-ahead log | Transfer 30 from A to B, but the power fails after A is debited and before B is credited. | After recovery A = 100, B = 100: the half-finished transfer left no trace. All or nothing. | After restart A = 70, B = 100: 30 has left A and arrived nowhere. Half a transaction survived. |
| Consistency CHECK (balance >= 0) | Transfer 130 from A, which holds only 100. The rule: no balance may go negative. | The transfer was refused and A = 100, B = 100. The database moved from one valid state to another (here, the same one). | The transfer committed and A = -30. The total is still 200, but the data now breaks the business rule. |
| Isolation Lock rows when read for update | Two deposits to A at the same time: 50 and 20. Each reads the balance, adds, and writes it back. | A = 170: both deposits count, as if the transactions had run one after the other. | A = 120, not 170: the second deposit overwrote the first. A lost update. |
| Durability Flush the log at commit | Transfer 30 from A to B, commit, then lose power before the changed pages reach disk. | After recovery A = 70, B = 130: the committed transfer survived the crash, rebuilt from the log. | After recovery A = 100, B = 100: the client was told the transfer committed, but it is gone. |
The write-ahead log
Atomicity and durability both rest on one rule: log the change before writing it. Before a changed data page goes to disk, the log records describing that change must already be there; before a commit is acknowledged, the commit record must be there. After a crash, recovery reads the log and does two things:
- Redo: replay logged changes, so committed work whose pages never reached disk is restored.
- Undo: roll back changes by transactions that never committed, using the old values recorded in the log.
This lets a database use memory freely, writing pages whenever convenient (the "steal" and "no-force" policies), while still keeping every guarantee. PostgreSQL's WAL, MySQL InnoDB's redo and undo logs, SQL Server's transaction log and SQLite's WAL mode all work on this principle, as do the ARIES algorithms published by C. Mohan and colleagues at IBM in 1992.
Settings that weaken ACID
Waiting for the disk on every commit is the main cost of durability, so databases let you trade it away. These settings are fine for some data and dangerous for other data:
| Database | Setting | Effect |
|---|---|---|
| PostgreSQL | synchronous_commit = off | Commits return before the WAL is flushed; a crash can lose transactions from the last fraction of a second (up to three times wal_writer_delay), but never corrupts data. |
| PostgreSQL | fsync = off | Never waits for the disk at all. A crash can corrupt the database. Only for throwaway data. |
| MySQL (InnoDB) | innodb_flush_log_at_trx_commit = 0 or 2 | The redo log is flushed about once a second instead of at each commit; up to a second of commits can be lost. The default, 1, flushes at every commit. |
| SQLite | PRAGMA synchronous = OFF | No syncing; a power loss can corrupt the database file. |
| Any | Isolation level | Read committed, the common default, allows lost updates and other anomalies that serializable prevents. |
Hardware matters too: a disk that reports a write as done while it is still in a volatile cache, without power-loss protection, can break durability whatever the database does.
ACID across machines
When a transaction changes data on several servers, atomicity needs every server to commit or none. The classic protocol is two-phase commit: a coordinator asks every participant to prepare (make its part durable and promise to commit), and only if all agree tells them all to commit. Its weakness is that participants that have prepared must wait if the coordinator fails. Distributed SQL databases such as Spanner, CockroachDB and TiDB combine two-phase commit with consensus replication (Paxos or Raft), so that each participant and the coordinator's decision are themselves replicated and survive failures.
ACID vs BASE
BASE, "basically available, soft state, eventually consistent", was coined in the late 1990s by Eric Brewer and colleagues at Berkeley to describe large distributed systems that relax ACID, above all immediate consistency across replicas, to stay available and scale out. Early NoSQL databases were often described this way, offering atomic operations on single records but no multi-record transactions.
The two are no longer separate camps. MongoDB has supported multi-document ACID transactions since version 4.0 (2018), DynamoDB offers transactions across items, and distributed SQL databases provide ACID with horizontal scaling. The useful question is not "ACID or BASE" but which guarantees each piece of data needs, at what cost.
History
Jim Gray described the transaction as a unit of atomic, consistent and durable work in "The Transaction Concept: Virtues and Limitations" (1981). Two years later, Theo Härder and Andreas Reuter added isolation and coined the acronym in "Principles of Transaction-Oriented Database Recovery" (1983). Gray received the Turing Award in 1998 for his work on transaction processing.
ACID questions and answers
What are the ACID properties?
ACID stands for atomicity, consistency, isolation and durability, the guarantees a database gives for transactions. Atomicity: a transaction happens completely or not at all. Consistency: a transaction moves the database from one valid state to another, respecting its rules. Isolation: concurrent transactions do not see each other’s partial work. Durability: once a transaction commits, it survives crashes.
What is an example of the ACID properties?
A bank transfer of 30 from account A to account B. Atomicity means the debit and the credit happen together or not at all, even if the server crashes between them. Consistency means a rule such as "no negative balances" is never broken. Isolation means two transfers at the same time cannot overwrite each other’s updates. Durability means that once the bank says the transfer is done, a power cut cannot undo it.
How do databases make transactions atomic?
Most databases record every change in a log before applying it. If a transaction fails or the server crashes, the database uses the log’s before-images to undo changes from transactions that never committed. Databases with multi-version concurrency control, such as PostgreSQL, also keep old row versions, so an aborted transaction’s new versions are simply never made visible.
How do databases guarantee durability?
By writing the transaction’s log records to disk and waiting for the disk to confirm (fsync) before reporting the commit. After a crash, the database replays the log to restore committed changes that had not yet reached the data files. Many databases let you relax this for speed, for example PostgreSQL’s synchronous_commit = off or MySQL’s innodb_flush_log_at_trx_commit = 2, at the risk of losing the last moments of commits.
Is consistency in ACID the same as in the CAP theorem?
No. ACID consistency means a transaction keeps the database valid according to its rules: constraints, foreign keys and the application’s own invariants. CAP consistency means linearizability: every read sees the most recent write across replicas. The two only share a word.
What is the difference between ACID and BASE?
BASE, “basically available, soft state, eventually consistent”, describes systems that give up some ACID guarantees, especially immediate consistency across replicas, in exchange for availability and scale. Many NoSQL databases were described that way. The line has blurred: MongoDB has supported multi-document ACID transactions since version 4.0, and distributed SQL databases offer ACID across machines.
Which databases are ACID compliant?
PostgreSQL, MySQL with InnoDB, SQL Server, Oracle and SQLite all provide ACID transactions, as do distributed databases such as CockroachDB, Spanner and TiDB. Their guarantees depend on configuration: the isolation level, and whether commits wait for the log to reach disk.
Who coined the term ACID?
Theo Härder and Andreas Reuter, in their 1983 paper “Principles of Transaction-Oriented Database Recovery”. They built on Jim Gray’s 1981 paper “The Transaction Concept: Virtues and Limitations”, which described atomicity, consistency and durability.
Further reading
- Jim Gray, 1981The Transaction Concept: Virtues and LimitationsWhere the idea of a transaction as a unit of work was laid out.
- PostgreSQL documentationWrite-Ahead Logging (WAL)How PostgreSQL makes commits durable and recovers after crashes.
- PostgreSQL documentationAsynchronous CommitWhat synchronous_commit = off risks, precisely.
- MySQL documentationinnodb_flush_log_at_trx_commitInnoDB's durability setting and its trade-offs.
- SQLite documentationAtomic Commit In SQLiteA detailed walk through making a commit atomic on real disks.
- SemicolonyIsolation levels simulatorThe I in ACID, anomaly by anomaly.