MySQL Transaction Management
A transaction is a group of SQL statements that MySQL treats as a single unit: either all of them take effect, or none do. Transferring money, placing an order with several items, or moving stock between warehouses all need transactions, so a failure halfway through never leaves the data in a broken state.
1. ACID: What Transactions Guarantee
| Property | Meaning | In the transfer example |
|---|---|---|
| Atomicity | All or nothing | Debit and credit both happen, or neither |
| Consistency | Rules always hold | The CHECK keeps balances from going negative |
| Isolation | Concurrent transactions don't see each other's half-done work | Others never see money "in flight" |
| Durability | Committed changes survive crashes | After COMMIT the transfer is safe on disk |
2. START TRANSACTION and COMMIT
Table: accounts (3 rows)
| acc_no | holder | balance |
|---|---|---|
| 101 | Asha | 10000.00 |
| 102 | Bilal | 3000.00 |
| 103 | Chen | 500.00 |
START TRANSACTION;
UPDATE accounts SET balance = balance - 2000 WHERE acc_no = 101;
UPDATE accounts SET balance = balance + 2000 WHERE acc_no = 102;
INSERT INTO transfers (from_acc, to_acc, amount) VALUES (101, 102, 2000);
COMMIT;
SELECT acc_no, holder, balance FROM accounts;
Result: 3 rows
| acc_no | holder | balance |
|---|---|---|
| 101 | Asha | 8000.00 |
| 102 | Bilal | 5000.00 |
| 103 | Chen | 500.00 |
BEGIN and BEGIN WORK are synonyms for START TRANSACTION.
3. ROLLBACK
Chen tries to send ₹800 but has only ₹500. The CHECK constraint rejects the debit, so the application rolls back the whole transfer:
START TRANSACTION;
UPDATE accounts SET balance = balance + 800 WHERE acc_no = 101; -- credit first
UPDATE accounts SET balance = balance - 800 WHERE acc_no = 103; -- fails: would go negative
Query OK, 0 rows affected
Query OK, 1 row affected
Rows matched: 1 Changed: 1 Warnings: 0
ERROR 3819 (HY000): Check constraint 'accounts_chk_1' is violated.
ROLLBACK;
SELECT acc_no, balance FROM accounts;
Result: 3 rows
| acc_no | balance |
|---|---|
| 101 | 8000.00 |
| 102 | 5000.00 |
| 103 | 500.00 |
4. Autocommit Mode
By default MySQL runs in autocommit mode: every statement outside an explicit transaction is committed immediately. START TRANSACTION suspends autocommit until COMMIT or ROLLBACK. You can also turn it off for the session:
SELECT @@autocommit AS autocommit_on;
SET autocommit = 0;
UPDATE accounts SET balance = balance + 1 WHERE acc_no = 103;
ROLLBACK; -- works: the update wasn't committed yet
SELECT balance FROM accounts WHERE acc_no = 103;
SET autocommit = 1;
Default
| autocommit_on |
|---|
| 1 |
After ROLLBACK
| balance |
|---|
| 500.00 |
5. Savepoints: Partial Rollback
A savepoint marks a point inside a transaction you can roll back to, without abandoning everything:
START TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE acc_no = 101; -- fee
SAVEPOINT after_fee;
UPDATE accounts SET balance = balance + 5000 WHERE acc_no = 103; -- wrong bonus
ROLLBACK TO SAVEPOINT after_fee; -- undo only the bonus
COMMIT;
SELECT acc_no, balance FROM accounts;
Result: 3 rows
| acc_no | balance |
|---|---|
| 101 | 7900.00 |
| 102 | 5000.00 |
| 103 | 500.00 |
6. Statements That Commit Implicitly
DDL statements (CREATE, ALTER, DROP, TRUNCATE, RENAME) and some others (LOCK TABLES, START TRANSACTION itself) commit any open transaction first. They can't be rolled back:
START TRANSACTION;
UPDATE accounts SET balance = balance + 999 WHERE acc_no = 102;
CREATE TABLE audit_note (txt VARCHAR(20)); -- implicit COMMIT of the update!
ROLLBACK;
SELECT balance FROM accounts WHERE acc_no = 102;
Result: 1 row
| balance |
|---|
| 5999.00 |
The ₹999 stayed, because the CREATE TABLE committed it before the ROLLBACK.
7. Isolation: What Other Sessions See
| Level | Dirty reads | Non-repeatable reads | Phantoms | Notes |
|---|---|---|---|---|
| READ UNCOMMITTED | Possible | Possible | Possible | Rarely used |
| READ COMMITTED | No | Possible | Possible | Each statement sees the latest committed data |
| REPEATABLE READ (default) | No | No | Mostly prevented in InnoDB | The whole transaction sees one snapshot |
| SERIALIZABLE | No | No | No | Plain SELECTs take shared locks; least concurrency |
Watch REPEATABLE READ with two real sessions. Session A starts a transaction and reads Bilal's balance:
START TRANSACTION;
SELECT balance FROM accounts WHERE acc_no = 102;
Result: 1 row
| balance |
|---|
| 5999.00 |
Session B (autocommit) changes it:
UPDATE accounts SET balance = balance + 1000 WHERE acc_no = 102;
Query OK, 1 row affected
Rows matched: 1 Changed: 1 Warnings: 0
Session A still sees its original snapshot until it commits:
SELECT balance FROM accounts WHERE acc_no = 102;
COMMIT;
SELECT balance FROM accounts WHERE acc_no = 102;
Inside the transaction: unchanged
| balance |
|---|
| 5999.00 |
After COMMIT: Session B's change is visible
| balance |
|---|
| 6999.00 |
Change the level per session with SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;.
8. Locks and Waiting
Writes lock the rows they change until the transaction ends. Another session that wants the same row waits (here it gives up after 2 seconds):
START TRANSACTION;
UPDATE accounts SET balance = balance - 1 WHERE acc_no = 101;
Query OK, 0 rows affected
Query OK, 1 row affected
Rows matched: 1 Changed: 1 Warnings: 0
UPDATE accounts SET balance = balance + 1 WHERE acc_no = 101;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
ROLLBACK;
Query OK, 0 rows affected
9. Transactions from Java
try (Connection con = DriverManager.getConnection(url, user, pass)) {
con.setAutoCommit(false); // START TRANSACTION
try (PreparedStatement debit = con.prepareStatement("UPDATE accounts SET balance = balance - ? WHERE acc_no = ?");
PreparedStatement credit = con.prepareStatement("UPDATE accounts SET balance = balance + ? WHERE acc_no = ?")) {
debit.setBigDecimal(1, amount); debit.setInt(2, fromAcc); debit.executeUpdate();
credit.setBigDecimal(1, amount); credit.setInt(2, toAcc); credit.executeUpdate();
con.commit(); // COMMIT
} catch (SQLException e) {
con.rollback(); // ROLLBACK
throw e;
}
}
// Spring: annotate the service method with @Transactional and Spring does the same for you.
10. Summary
- A transaction makes several statements all-or-nothing (ACID); InnoDB supports it.
START TRANSACTION…COMMITsaves;ROLLBACKundoes;SAVEPOINTallows partial undo.- Autocommit is on by default; DDL commits implicitly.
- REPEATABLE READ (default) gives each transaction a consistent snapshot.
- Keep transactions short; in Java use setAutoCommit(false) or Spring's @Transactional.