SQL Transactions and ACID
A transaction groups several statements into one unit of work that either succeeds completely or leaves no trace. Moving money between two accounts is the textbook case: the debit and the credit must both happen, or neither. This lesson runs real transactions on MySQL 8.4 and explains the ACID guarantees behind them.
- Start, commit and roll back a transaction
- Undo part of a transaction with
SAVEPOINT - What autocommit does
- The four ACID properties and the isolation levels
The problem transactions solve
Transfer 2,000 from Asha’s account to Ravi’s account. That is two updates. If the server crashes, or the second update fails, after the first one, 2,000 disappears. A transaction makes the two updates atomic.
Every example in this lesson starts with this table:
CREATE TABLE accounts ( account_id INT PRIMARY KEY, holder VARCHAR(30) NOT NULL, balance INT NOT NULL CHECK (balance >= 0));INSERT INTO accounts VALUES (1, 'Asha', 10000), (2, 'Ravi', 3000);START TRANSACTION and COMMIT
CREATE TABLE accounts (account_id INT PRIMARY KEY, holder VARCHAR(30) NOT NULL, balance INT NOT NULL CHECK (balance >= 0));INSERT INTO accounts VALUES (1, 'Asha', 10000), (2, 'Ravi', 3000); START TRANSACTION;UPDATE accounts SET balance = balance - 2000 WHERE account_id = 1;UPDATE accounts SET balance = balance + 2000 WHERE account_id = 2;COMMIT; SELECT * FROM accounts;| account_id | holder | balance |
|---|---|---|
| 1 | Asha | 8000 |
| 2 | Ravi | 5000 |
START TRANSACTION(orBEGIN) opens a transaction. From now on, changes are pending.- The two
UPDATEs move the money. Other sessions still see the old balances. COMMITmakes both changes permanent and visible to everyone at the same moment.- The total money in the bank is still 13,000.
ROLLBACK
ROLLBACK throws away every change since START TRANSACTION:
CREATE TABLE accounts (account_id INT PRIMARY KEY, holder VARCHAR(30) NOT NULL, balance INT NOT NULL CHECK (balance >= 0));INSERT INTO accounts VALUES (1, 'Asha', 10000), (2, 'Ravi', 3000); START TRANSACTION;UPDATE accounts SET balance = 0 WHERE account_id = 1;SELECT * FROM accounts;ROLLBACK;SELECT * FROM accounts;| account_id | holder | balance |
|---|---|---|
| 1 | Asha | 0 |
| 2 | Ravi | 3000 |
| account_id | holder | balance |
|---|---|---|
| 1 | Asha | 10000 |
| 2 | Ravi | 3000 |
- Inside the transaction Asha’s balance is set to 0, and this session sees the change.
ROLLBACKundoes it.- The final
SELECTshows the original 10,000.
When a statement fails mid-transaction
Ravi tries to send 5,000 but has only 3,000. The credit to Asha runs first, then the debit breaks the CHECK (balance >= 0) rule:
CREATE TABLE accounts (account_id INT PRIMARY KEY, holder VARCHAR(30) NOT NULL, balance INT NOT NULL CHECK (balance >= 0));INSERT INTO accounts VALUES (1, 'Asha', 10000), (2, 'Ravi', 3000); START TRANSACTION;UPDATE accounts SET balance = balance + 5000 WHERE account_id = 1;UPDATE accounts SET balance = balance - 5000 WHERE account_id = 2;ERROR 3819 (HY000): Check constraint 'accounts_chk_1' is violated.
The failed statement changed nothing, but the transaction is still open, and the first update is still pending:
SELECT * FROM accounts;ROLLBACK;SELECT * FROM accounts;| account_id | holder | balance |
|---|---|---|
| 1 | Asha | 15000 |
| 2 | Ravi | 3000 |
| account_id | holder | balance |
|---|---|---|
| 1 | Asha | 10000 |
| 2 | Ravi | 3000 |
- The first
SELECTshows Asha at 15,000: MySQL does not undo the whole transaction automatically when one statement fails. - Your code must react to the error by calling
ROLLBACK. - After the rollback both balances are back to 10,000 and 3,000.
In application code, always wrap transactions in try/except (or try/catch): on any error, ROLLBACK; only when every statement succeeded, COMMIT.
SAVEPOINT: partial rollback
A savepoint marks a point inside a transaction that you can roll back to without losing the earlier work:
CREATE TABLE accounts (account_id INT PRIMARY KEY, holder VARCHAR(30) NOT NULL, balance INT NOT NULL CHECK (balance >= 0));INSERT INTO accounts VALUES (1, 'Asha', 10000), (2, 'Ravi', 3000); START TRANSACTION;UPDATE accounts SET balance = balance - 1000 WHERE account_id = 1;SAVEPOINT after_fee;UPDATE accounts SET balance = balance + 9999 WHERE account_id = 2;ROLLBACK TO SAVEPOINT after_fee;COMMIT; SELECT * FROM accounts;| account_id | holder | balance |
|---|---|---|
| 1 | Asha | 9000 |
| 2 | Ravi | 3000 |
- A fee of 1,000 is taken from Asha.
SAVEPOINT after_feeremembers this point.- A wrong credit to Ravi is made, then undone with
ROLLBACK TO SAVEPOINT after_fee. COMMITsaves the fee only: Asha 9,000, Ravi unchanged.
Autocommit
By default MySQL runs in autocommit mode: every statement outside an explicit transaction is committed immediately.
SELECT @@autocommit;| @@autocommit |
|---|
| 1 |
SET autocommit = 0; keeps a transaction open until you COMMIT or ROLLBACK. Most applications leave autocommit on and use START TRANSACTION where they need several statements to succeed together.
DDL statements such as CREATE, ALTER, DROP and TRUNCATE cause an implicit commit in MySQL: they commit any open transaction and cannot be rolled back.
ACID
| Property | Means | In the transfer example |
|---|---|---|
| Atomicity | all or nothing | debit and credit both happen, or neither |
| Consistency | rules always hold | CHECK (balance >= 0) is never broken |
| Isolation | concurrent transactions do not see each other’s half-done work | nobody sees the money “in flight” |
| Durability | committed data survives a crash | after COMMIT, a power cut cannot undo the transfer |
MySQL’s default storage engine, InnoDB, provides all four.
Isolation levels
SELECT @@transaction_isolation;| @@transaction_isolation |
|---|
| REPEATABLE-READ |
| Level | Dirty read | Non-repeatable read | Phantom read |
|---|---|---|---|
| READ UNCOMMITTED | possible | possible | possible |
| READ COMMITTED | no | possible | possible |
| REPEATABLE READ (MySQL default) | no | no | mostly prevented in InnoDB |
| SERIALIZABLE | no | no | no |
- Dirty read: seeing another transaction’s uncommitted change.
- Non-repeatable read: reading the same row twice and getting different values.
- Phantom read: running the same query twice and getting new rows.
Change it for the next transaction with SET TRANSACTION ISOLATION LEVEL READ COMMITTED;.
Locking reads
When you read a row in order to update it, lock it so nobody changes it in between:
CREATE TABLE accounts (account_id INT PRIMARY KEY, holder VARCHAR(30) NOT NULL, balance INT NOT NULL CHECK (balance >= 0));INSERT INTO accounts VALUES (1, 'Asha', 10000), (2, 'Ravi', 3000); START TRANSACTION;SELECT balance FROM accounts WHERE account_id = 1 FOR UPDATE;UPDATE accounts SET balance = balance - 500 WHERE account_id = 1;COMMIT;SELECT * FROM accounts;| balance |
|---|
| 10000 |
| account_id | holder | balance |
|---|---|---|
| 1 | Asha | 9500 |
| 2 | Ravi | 3000 |
SELECT ... FOR UPDATEreads Asha’s balance and locks her row until the transaction ends.- Another session trying to update or lock the same row waits.
- After
COMMITthe lock is released.
Quick quiz
1. What does ROLLBACK do?
Every pending change of the transaction is discarded.
2. A statement fails inside a MySQL transaction. What happens to the earlier statements?
MySQL only rolls back the failed statement; the application decides about the rest.
3. Which ACID property guarantees that committed data survives a crash?
Durability means a committed change is permanent.
4. What is MySQL InnoDB’s default isolation level?
@@transaction_isolation shows REPEATABLE-READ.
Transactions FAQ
What is a transaction in SQL?
A transaction is a group of SQL statements executed as one unit: either all of them are committed, or all are rolled back.
What is the difference between COMMIT and ROLLBACK?
COMMIT makes the changes of the current transaction permanent. ROLLBACK discards them.
What are ACID properties?
Atomicity (all or nothing), Consistency (constraints always hold), Isolation (concurrent transactions do not interfere) and Durability (committed data survives failures).
Can I roll back a TRUNCATE or DROP in MySQL?
No. DDL statements commit implicitly in MySQL, so they cannot be undone with ROLLBACK.