Core CS · DBMS
Two hundred rupees, two writes, one promise
A transfer is two writes, and there is no correct state of the world in which only the first one happened. You will walk one transfer through all five transaction states, cut the power halfway, and watch the log put the money back.
Move 200 rupees, then cut the power →01 The idea
One unit of work, or none of it
Almost nothing useful is one write. Moving 200 rupees from account A to account B is a debit and a credit, and the two are only meaningful together. Between them the database holds a state that is arithmetically false: 200 rupees have left A and arrived nowhere. Every database has to pass through that state, because it has no way to change two rows at the same instant.
A transaction is how you tell the database that a group of reads and writes is one act. You open it, you do the work, and you end it with COMMIT or ROLLBACK. In exchange the database promises four things about that group, and those four promises are the letters of ACID. They are not four features bolted on to each other; they are four different answers to the same question, which is what a partly finished piece of work is allowed to do to everybody else.
Keep one pair of numbers in your head for the rest of this lesson. Account A holds 1,000 and account B holds 500, so A plus B is 1,500. That total is the invariant: a transfer moves money between the accounts, it never creates or destroys any. Halfway through the transfer the total reads 1,300. Everything in this lesson is about who is allowed to see that 1,300, and what happens if the machine dies while it is on screen.
02 Worked example
One transfer, five states
Here is the whole transfer written out the way a textbook writes it, with the balances after every operation. read and write are the transaction talking to the database; the assignment lines happen inside the transaction and touch nothing on disk.
-- T1 : move 200 from A to B A = 1000 B = 500 A + B = 1500read(A) A = 1000A := A - 200 A = 800 inside T1 only, nothing on diskwrite(A) A = 800 A + B = 1300 <-- broken, and legalread(B) B = 500B := B + 200 B = 700 inside T1 only, nothing on diskwrite(B) B = 700 A + B = 1500 <-- repaired, not promisedCOMMIT A = 800 B = 700 A + B = 1500 promised
Line 4 is the interesting line, not line 8. After it the two accounts hold 800 and 500, which totals 1,300, and the bank is 200 rupees short. Line 7 repairs that: 800 and 700 total 1,500 again. But repaired is not the same as promised, and the five states below exist to keep those two ideas apart.
The strip runs left to right, so two arrows are missing from it and both are examinable. Active can go straight to failed, and so can partially committed. Those are the only two ways node 4 is ever reached, which is why nothing is drawn entering it. The dot between nodes 3 and 4 is not an arrow: committed never becomes failed. The table in the next section lists every legal move.
Node 2 is highlighted because it is the one students collapse into node 3. Between them sits a disk flush. The transaction has finished thinking, the new balances are correct, and none of that is worth anything yet: the changed pages may still be in the buffer pool in memory, the commit record may not have reached disk, and if the machine dies here the transfer did not happen. Partially committed is not a formality on the way to committed. It is a state with two exits.
Nodes 3 and 5 share one property: a transaction that reaches either of them is terminated. Terminated is not a sixth thing that happens to a transaction. It is the name for having stopped: no locks held, no row in the transaction table, nothing left to decide. You can start a new transaction that does the same work, but you cannot resume this one.
03 Mechanics
The five states, and the four letters
The exam question is never “name the states”. It is “from this state, where can it legally go, and who moves it”. Read the third column as the whole answer and the fourth as the follow-up.
| State | What is true of the data | Legal next states | Who makes that move |
|---|---|---|---|
| Active | BEGIN has run and statements are executing. Every write already made has its old value in the log, so all of it is undoable. | Partially committed, or Failed |
Your statements move it forward. A constraint, a deadlock victim notice, a ROLLBACK or a crash moves it sideways. |
| Partially committed | the last statement has executed. The new values may exist only in the buffer pool, and no commit record has reached stable storage |
Committed, or Failed |
The recovery manager, by forcing the log out to disk, or by failing to. |
| Committed | The transaction’s log records, including its commit record, are on stable storage. The change is permanent and visible to everybody. | none, committed is an end state |
Nothing moves it anywhere. A committed transaction is terminated on arrival, and there is no arrow back to active, failed or aborted. That missing arrow is exactly what durability buys you. |
| Failed | normal execution cannot continue. No further work will be done on behalf of this transaction |
Aborted only |
Whatever detected the failure hands it to the recovery manager. You do not get a choice here. |
| Aborted | Every write this transaction made has been undone from the log. The database looks exactly as it did before BEGIN. | none, aborted is an end state |
The recovery manager gets it here by applying the undo records backwards. Once it arrives, the transaction is terminated. |
| Terminated (a label, not a sixth state) |
The name for having reached committed or aborted, so it is not a node you can draw an arrow into. It holds no locks and has no row in the transaction table. | none, it is over |
Nothing, because nothing is left to move. The application may start a fresh transaction that does the same work. It cannot resume this one. |
Two edges in that table are the ones interviewers probe. Partially committed to failed is the first, because candidates treat the last statement finishing as the finish line. Committed back to anywhere is the second, because there is no such edge: a committed transaction cannot be returned to active, failed or aborted, by you or by recovery. Everything else in this topic is one of those two edges with a different name on it.
Now the four promises, and who inside the database actually keeps each one. The third column is the version to give an interviewer, because it is the failure and not the definition.
| Letter | The promise, stated precisely | What you would see without it, on our transfer | Who provides it |
|---|---|---|---|
| A · Atomicity | Every write of the transaction takes effect, or none of them does. There is no third outcome. | A = 800 and B = 500 after the restart: 200 rupees left A and arrived nowhere, and A + B = 1300 where 1500 was |
The recovery manager. It puts the old value in the log before each write, and applies those records backwards on abort or restart. |
| C · Consistency | A transaction that starts from a valid database leaves a valid database. It says nothing at all about the middle. | a transfer that debits 200 from A and credits 150 to B: atomic, durable, and 50 rupees short |
Shared. The DBMS enforces what you declared as NOT NULL, CHECK and FOREIGN KEY. You supply the rest, inside the transaction’s own logic. |
| I · Isolation | Running transactions concurrently produces a result equal to running the same transactions one after another in some order. | another session sums the accounts while A is 800 and B is still 500, and reports 1300, a total the bank never held. Reading an uncommitted value like that is a dirty read, and READ UNCOMMITTED is exactly the level that permits it |
The concurrency-control manager. Two-phase locking or multiversion snapshots, exposed to you as isolation levels. |
| D · Durability | Once COMMIT has returned, the change survives a crash of the process, the operating system or the machine. | COMMIT returns, the customer’s phone says the transfer is done, the power goes out, and after the restart it did not happen |
The recovery manager again, through write-ahead logging: the log and the commit record reach stable storage before COMMIT returns. |
Write-ahead logging is two ordering rules, not one, and both of them are worth stating out loud. First: the log record describing a change must reach stable storage before the changed data page does. That is what makes undo possible, because if the page went out first and the machine died, nothing on disk would say what the old value had been. Second: all of a transaction’s log records, including its commit record, must reach stable storage before COMMIT returns to your application. That is what makes redo possible, and it is exactly the promise durability makes. The instant the second rule is satisfied is the commit point, and it is the line the whole lesson runs along.
Written in SQL the entire mechanism is four keywords. What you never write is the interesting part.
BEGIN; -- state: ACTIVE UPDATE account SET bal = bal - 200 WHERE id = 'A'; -- 1000 -> 800 UPDATE account SET bal = bal + 200 WHERE id = 'B'; -- 500 -> 700COMMIT; -- PARTIALLY COMMITTED, flush, then COMMITTED -- anything at all goes wrong between lines 1 and 4:ROLLBACK; -- FAILED, then ABORTED. A is 1000 again, B is 500. -- the part of consistency the database can hold for you:ALTER TABLE account ADD CONSTRAINT bal_min CHECK (bal >= 0);-- the part it cannot: that the 200 you subtract is the 200 you add.
Line 4 is three states in one keyword: the transaction becomes partially committed when the last statement before it finished, the log is forced to disk, and only then is it committed and COMMIT allowed to return. Line 7 is the other branch, and notice you did not have to say what to undo: the log already knows, because line 2 wrote A was 1000 before it touched the page. Line 10 is the slice of consistency that can be declared once and is then tested on every write by every writer. Line 11 is the slice that cannot, and no database has ever solved it.
Three vendor differences worth one clause each, because interviewers use them to check whether you have typed this or only read it. BEGIN is START TRANSACTION in standard SQL and both work in PostgreSQL and MySQL, while Oracle has neither: a transaction there starts implicitly at your first executable SQL statement. DDL like line 10 commits implicitly in Oracle and MySQL, so it silently ends any transaction it lands inside, while PostgreSQL and SQL Server keep DDL inside the transaction, so on those two a schema change can be rolled back. And a CHECK is tested on the statement that writes the row rather than held back until COMMIT, which matters because it decides whether a violation aborts you at line 2 or at line 4; PostgreSQL, MySQL and SQL Server do not let you defer a CHECK at all, and Oracle does.
05 Cheat sheet
ACID on one card
Four rows. Column two is a sentence you can say in one breath, column three is what you name when they ask how, and column four is the follow-up that is coming whether you are ready for it or not.
| Letter | Say this | Name this mechanism | The follow-up they ask |
|---|---|---|---|
| A · Atomicity | “All the writes happen or none of them do. There is no half.” | the log: an undo record before every write, applied backwards on abort or restart |
“It was partially committed when the machine died. Is that safe?” |
| C · Consistency | “Valid before, valid after. It says nothing about the middle.” | declared constraints for the part you can write down, the transaction’s own logic for the rest |
“So whose job is C, yours or the database’s?” |
| I · Isolation | “Concurrent transactions give the same result as some serial order of them.” | two-phase locking or MVCC, exposed to you as isolation levels |
“What does a level below SERIALIZABLE actually let in?” |
| D · Durability | “Once COMMIT returns, a crash cannot take it back.” | write-ahead logging: log and commit record on stable storage before COMMIT returns |
“Can you switch that off, and what do you get for it?” |
06 Where & why
Where COMMIT actually costs you something
Durability is the only letter with a price tag you can read off a config file, and every serious engine gives you a knob for it. Knowing where those knobs are is the difference between a candidate who has read the four words and one who has watched a commit wait on a disk.
Every commit writes the transaction’s records and its commit record to the write-ahead log and flushes them before COMMIT returns. Setting synchronous_commit = off lets COMMIT return before that flush: atomicity, consistency and isolation are untouched, and you agree to lose the last fraction of a second of committed transactions if the machine dies. It is the clearest example anywhere of trading one letter for throughput on purpose. PostgreSQL also keeps DDL inside the transaction, so you can BEGIN, ALTER TABLE, and roll the schema change back. SQL Server allows that too; Oracle and MySQL commit the instant the DDL runs.
InnoDB gives you all four letters, with a redo log doing the job PostgreSQL’s WAL does. MyISAM, the old default, gives you none of them: no working ROLLBACK, no crash recovery of this kind, and BEGIN that buys you nothing. On InnoDB the durability knob is innodb_flush_log_at_trx_commit: 1 writes and flushes at every commit, 2 and 0 flush roughly once a second and trade durability for speed. Two traps to remember: DDL commits implicitly, so a CREATE TABLE inside your block ends the transaction, and the default isolation level is REPEATABLE READ where most other engines use READ COMMITTED.
A transaction starts implicitly at your first executable SQL statement, which in day-to-day work means your first INSERT, UPDATE or DELETE; there is no BEGIN statement to type, and you end it with COMMIT or ROLLBACK. DDL commits implicitly here too. Oracle keeps undo data in dedicated undo tablespaces rather than mixing it into the redo log, which is how another session is served the pre-change version of a row instead of waiting behind your uncommitted write. SAVEPOINT lets you undo part of a transaction, and the levels on offer are READ COMMITTED, the default, and SERIALIZABLE. READ UNCOMMITTED is not on the menu.
SQLite keeps a rollback journal or a WAL file beside the database, so a transfer interrupted by a power cut is undone the next time the file is opened, with no server and no DBA anywhere. Its trap is autocommit: every statement you run outside an explicit BEGIN is its own transaction with its own flush to disk. That is why a loop of ten thousand inserts is painfully slow in SQLite and fast the moment you wrap the loop in one BEGIN and one COMMIT. You are not switching durability off there; you are asking for it ten thousand times fewer.
07 Interview questions
What they actually ask
Transactions come up in almost every DBMS interview, usually as “what is ACID” and almost always answered as four expanded words. The candidates who get through name the component behind each letter and can point at the exact instant the guarantee starts.
What is a transaction?
Why is a bank transfer the standard example of one?
Walk me through the states a transaction goes through.
A transaction is partially committed. Can it still fail?
What is the commit point?
Atomicity and consistency sound like the same thing. Distinguish them.
Define isolation without using the word isolation.
What is write-ahead logging, and which letters of ACID does it buy?
Which part of the DBMS gives you which letter?
Can you roll back a transaction that has already committed?
Do I have to write BEGIN and COMMIT, or is it automatic?
Isolation is the letter people relax in practice. What does relaxing it buy, and what does it cost?
08 Practice problems
Six to work through
Every one of these can be done with a pen and the numbers 1,000 and 500. Write the balances down after each step before you name a letter. Naming the letter first is how people end up answering a durability question with a constraint.