ACID
What atomicity, consistency, isolation and durability actually guarantee in InnoDB — and what they leave to you.
START TRANSACTION; ... COMMIT; -- or ROLLBACK;ACID is the set of four promises a transactional storage engine makes about a unit of work. In MySQL 8.4 the default engine, InnoDB, makes all four. MyISAM makes none of them: it has no transactions, so START TRANSACTION and ROLLBACK are accepted there but change nothing — the rollback reports only a warning, Some non-transactional changed tables couldn't be rolled back.
The four letters
- Atomicity — the statements between
START TRANSACTIONandCOMMITall take effect or none do. Backed by the undo log, and byROLLBACK. - Consistency — the manual maps this to the
InnoDBdoublewrite buffer and crash recovery: the data on disk is never left as a half-written page. - Isolation — a transaction does not see another transaction's unfinished work. Controlled by the isolation level and by locking.
- Durability — once
COMMITreturns, the change survives a crash. Depends oninnodb_flush_log_at_trx_commit,sync_binlog, and on the disk actually honouringfsync().
What it does not cover
The C in ACID is not "my business rules hold". Nothing in a transaction stops you committing a negative balance — that is the job of NOT NULL, CHECK, UNIQUE and FOREIGN KEY.
Atomicity also stops at DDL. And a statement error does not roll the transaction back for you: MySQL undoes the failed statement and leaves the transaction open, so an application that ignores the error can commit a half-finished unit of work.
Example
-- Atomicity in practice: both balances move, or neither does.
-- ACID only applies on a transactional engine, so check first.
SELECT TABLE_NAME, ENGINE
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME IN ('accounts', 'ledger'); -- both must report InnoDB
START TRANSACTION;
UPDATE accounts SET balance = balance - 250.00 WHERE id = 7;
UPDATE accounts SET balance = balance + 250.00 WHERE id = 12;
INSERT INTO ledger (from_account, to_account, amount, created_at)
VALUES (7, 12, 250.00, NOW());
-- Isolation: no other session sees any of the three changes until here.
-- Durability: once this returns, a power cut cannot lose them.
COMMIT;
-- If the application had raised an error after the first UPDATE, it would
-- issue ROLLBACK instead, and the accounts table would be untouched.When to use it
- Deciding whether a multi-row change needs an explicit transaction or can stay in autocommit.
- Auditing an inherited schema for MyISAM tables that silently ignore ROLLBACK.
- Explaining to a team why committed rows disappeared after a crash on a server with relaxed flush settings.
- Working out why a partially applied update got committed despite an error in the middle.
More examples
A failed statement does not abort the transaction
Atomicity is a guarantee about what COMMIT and ROLLBACK do, not an automatic abort. MySQL undoes the statement that failed and leaves the transaction open. Any client library or hand-written script that swallows an error and then commits will persist a partial unit of work. Reach for this check whenever you write transaction handling by hand rather than through a framework.
START TRANSACTION;
UPDATE accounts SET balance = balance - 250.00 WHERE id = 7;
-- Suppose this violates a CHECK constraint and returns an error.
UPDATE accounts SET balance = balance + 250.00 WHERE id = 99999;
-- ERROR 3819 (HY000): Check constraint 'chk_balance_non_negative' is violated.
-- MySQL has rolled back ONLY the failing statement. The first UPDATE
-- is still pending. Committing now would destroy 250.00.
ROLLBACK; -- this is the correct response, not COMMITInspecting the durability settings
The D in ACID is a configuration choice, not a constant. Hosted MySQL and tuned-for-throughput setups often relax these two variables, which means a committed transaction can vanish in a crash. Check them before you promise anyone that a commit is final — especially on a replica or a staging box that was tuned for import speed.
SELECT @@GLOBAL.innodb_flush_log_at_trx_commit AS flush_at_commit,
@@GLOBAL.sync_binlog AS sync_binlog;
-- innodb_flush_log_at_trx_commit = 1
-- log buffer written and flushed to disk at each commit: full ACID.
-- innodb_flush_log_at_trx_commit = 0 or 2
-- faster, but a crash can lose the last second or so of committed
-- transactions. This is a deliberate trade, not a bug.Consistency comes from constraints, not from the transaction
Shows the boundary between the engine's job and yours. InnoDB guarantees the write is atomic and durable; it has no opinion about whether the value is sensible. Declaring the rule as a CHECK, FOREIGN KEY or UNIQUE constraint is what makes an invalid state impossible, and it holds for every client, not just the one you wrote.
-- A transaction will happily commit nonsense. Constraints will not.
ALTER TABLE accounts
ADD CONSTRAINT chk_balance_non_negative CHECK (balance >= 0);
-- CHECK constraints are enforced from MySQL 8.0.16 onward.
START TRANSACTION;
UPDATE accounts SET balance = balance - 1000000 WHERE id = 7;
-- ERROR 3819 (HY000): Check constraint 'chk_balance_non_negative' is violated.
ROLLBACK;
Discussion