Why interviewers love this: ACID is the interview shorthand for whether your database can protect money, orders, and inventory when failures happen at the worst possible time.
Question: What are ACID properties in SQL?
Answer: ACID stands for Atomicity, Consistency, Isolation, and Durability. Together, they describe the guarantees a transaction should provide: all changes happen together or not at all, the data rules still hold, concurrent transactions do not interfere in unsafe ways, and a committed change survives a crash.
Interview-Ready Answer: In SQL, ACID is the set of transaction guarantees I rely on to keep data correct under failure and concurrency. Atomicity means all-or-nothing, consistency means the transaction leaves the data in a valid state, isolation means other transactions should not see my half-finished work, and durability means once I commit, the change survives a power loss or crash. Under the hood, databases usually combine locks or snapshots with write-ahead logging to make that happen.
Detailed Explanation:
CHECK constraints, and other invariants. Important nuance: the database does not magically know every business rule; your schema and application logic define what valid means.COMMIT. The usual mechanism is write-ahead logging, or WAL, which means the log is saved before the final success is acknowledged.BEGIN. The database creates transaction state and, in many engines, a snapshot of what this transaction is allowed to see.COMMIT, the database flushes the needed log records to stable storage, often forcing an fsync call. fsync means the operating system is told to push buffered data to disk, not just to memory.Isolation is not one single setting; SQL databases offer different levels, each trading correctness for concurrency.
| Level | What you get | What can still happen |
|---|---|---|
| Read Uncommitted | Fastest, weakest | Dirty reads |
| Read Committed | No dirty reads | Non-repeatable reads |
| Repeatable Read | Stable row reads | Phantom issues in some engines |
| Serializable | As if one-by-one | Most expensive, may abort conflicts |
Defaults vary: PostgreSQL defaults to READ COMMITTED, MySQL InnoDB often defaults to REPEATABLE READ, SQL Server defaults to READ COMMITTED, and Oracle uses READ COMMITTED. A strong interview point: SERIALIZABLE does not always mean transactions literally run one after another; it means the result is equivalent to some serial order.
A subtle gotcha is write skew: two transactions each read the same snapshot, make different updates, and together violate a rule. Snapshot-style isolation can still allow that in some engines, which is why true serializable mode matters for strict invariants.
Memory hook: think of a transaction like a sealed bank vault: either every item goes in together, the rules still hold, nobody sees the contents mid-transfer, and once the vault is locked, the contents stay safe.
Real-World Story: A checkout service for an online store creates an order row, reserves inventory, and records payment authorization. The team wraps those writes in one SQL transaction so a customer is never charged for an order that did not exist, and stock is never reduced without a matching order.
Now imagine a bug: an engineer moves the payment step outside the transaction because they want the code to feel faster. Under load, the service sometimes writes the order, then crashes before inventory is updated. Support starts seeing complaints like my card was charged but the order disappeared, while warehouse reports show items still available even though customers already bought them. Logs show retries, occasional unique-key errors, and mismatched order counts between the payments table and the orders table.
The outage is not just a code bug; it is a transaction boundary bug. ACID is what prevents the database from saving you halfway through a business action. Without it, your reports drift, your support queue fills up, and your retry logic can make the damage worse by creating duplicates or double-debits.
CREATE TEMPORARY TABLE accounts (
account_id INTEGER PRIMARY KEY,
owner VARCHAR(50) NOT NULL,
balance NUMERIC(12,2) NOT NULL CHECK (balance >= 0)
);
INSERT INTO accounts (account_id, owner, balance) VALUES
(1, 'Alice', 100.00),
(2, 'Bob', 50.00);
-- Transaction 1: a clean transfer.
-- Both updates succeed together, so the database never shows a half-finished transfer.
BEGIN TRANSACTION;
UPDATE accounts
SET balance = balance - 30.00
WHERE account_id = 1 AND balance >= 30.00;
UPDATE accounts
SET balance = balance + 30.00
WHERE account_id = 2;
COMMIT;
SELECT account_id, owner, balance
FROM accounts
ORDER BY account_id;
-- Transaction 2: a failure path.
-- We intentionally try to overdraw Alice. The CHECK constraint rejects the negative balance.
-- A SAVEPOINT lets us recover from just this bad step instead of losing the whole transaction.
BEGIN TRANSACTION;
SAVEPOINT risky_transfer;
UPDATE accounts
SET balance = balance - 200.00
WHERE account_id = 1;
-- After the error, we rewind to the savepoint and continue safely.
ROLLBACK TO SAVEPOINT risky_transfer;
-- Retry with a safe amount. This is the kind of fallback logic an application would use.
UPDATE accounts
SET balance = balance - 20.00
WHERE account_id = 1 AND balance >= 20.00;
UPDATE accounts
SET balance = balance + 20.00
WHERE account_id = 2;
COMMIT;
SELECT account_id, owner, balance
FROM accounts
ORDER BY account_id;Follow-up & Tricky Questions:
SERIALIZABLE is the strongest and usually the most expensive.Common Mistakes:
READ COMMITTED or REPEATABLE READ instead.Memory Hook: Sealed vault - all changes in or out together, rules stay valid, nobody peeks mid-transfer, and once locked, the result stays put.
Cheat Sheet:
Practice Tasks:
CHECK constraint and test how a bad update fails, then recover with a savepoint.READ COMMITTED and SERIALIZABLE in your own database by running two concurrent sessions and looking for anomalies.