Transaction Definition
Begin, Commit, Rollback
Read aloud is unavailable in this browser.
The Three Commands That Control Everything
In the vast landscape of SQL commands—hundreds of keywords for querying, modifying, and managing databases—three simple statements have outsized importance: BEGIN, COMMIT, and ROLLBACK. These transaction control statements determine whether your changes become permanent reality or vanish as if they never happened.
These commands may seem trivial, but understanding their exact semantics prevents catastrophic errors. What happens if you COMMIT twice? What if you forget to BEGIN? What does ROLLBACK do to locks? This page answers these questions with precision.
By the end of this page, you will understand the exact semantics of BEGIN, COMMIT, and ROLLBACK, how these commands differ across major database systems, what happens mechanically when each command executes, error handling patterns for transaction control, and advanced variations like COMMIT AND CHAIN.
The BEGIN Statement
The BEGIN statement (also START TRANSACTION in some systems) marks the starting point of a transaction. It tells the database: 'From this point forward, treat all operations as a single atomic unit until I say otherwise.'
Formal Semantics:
When BEGIN executes:
- A new transaction context is created
- A transaction identifier (XID/TXID) is assigned
- Isolation level settings take effect
- Read/write mode is established
- The connection enters 'in transaction' state
- Autocommit is suspended for this transaction
12345678910111213141516171819202122232425262728
-- Basic transaction startBEGIN; -- Equivalent formsSTART TRANSACTION; -- With explicit isolation levelBEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; -- With read-only hintBEGIN TRANSACTION READ ONLY; -- Combined optionsSTART TRANSACTION ISOLATION LEVEL REPEATABLE READ, READ ONLY; -- Check transaction statusSELECT txid_current(); -- Returns current transaction IDSELECT pg_is_in_recovery(); -- Check if replica -- Transaction state querySELECT pid, state, xact_start, query_start, NOW() - xact_start AS transaction_ageFROM pg_stat_activity WHERE pid = pg_backend_pid();In PostgreSQL, executing BEGIN while already in a transaction generates a warning but continues the existing transaction. In SQL Server, it increments @@TRANCOUNT, creating artificial 'nesting'. Always check transaction state before assuming you're starting fresh.
Transaction Options Explained:
| Option | Effect | Use Case |
|---|---|---|
| ISOLATION LEVEL | Sets how transaction sees other transactions' changes | SERIALIZABLE for financial; READ COMMITTED for general use |
| READ ONLY | Transaction will only read, not modify data | Reporting queries; optimizer hints for better plans |
| READ WRITE | Transaction may read and modify (default) | Most operations |
| WITH CONSISTENT SNAPSHOT (MySQL) | Creates point-in-time view of database | Ensuring consistent reads across multiple queries |
| WITH MARK (SQL Server) | Creates named recovery point in transaction log | Point-in-time recovery to specific business events |
The COMMIT Statement
The COMMIT statement makes all changes in the current transaction permanent. Once committed, changes survive any subsequent failure—this is the durability guarantee of ACID. COMMIT is the point of no return.
Formal Semantics:
When COMMIT executes:
- Log Record Creation — A commit record is added to the transaction log containing the transaction ID and timestamp
- Log Force — The log buffer is forced to stable storage (this is the critical durability point)
- Lock Release — All locks acquired by the transaction are released
- Resource Cleanup — Transaction context, temporary data structures, and undo information are cleaned up
- Visibility — Changes become visible to other transactions (exact timing depends on isolation level)
- Control Return — Application receives success confirmation
12345678910111213141516171819202122
-- Basic commitBEGIN; UPDATE accounts SET balance = balance - 100 WHERE id = 1; UPDATE accounts SET balance = balance + 100 WHERE id = 2;COMMIT; -- Equivalent formsCOMMIT WORK;COMMIT TRANSACTION;END; -- PostgreSQL alias for COMMIT -- Commit and start new transaction immediatelyCOMMIT AND CHAIN;-- Equivalent to: COMMIT; BEGIN;-- New transaction inherits same isolation level -- Prepared transaction (two-phase commit)BEGIN; UPDATE distributed_data SET value = 42 WHERE id = 1;PREPARE TRANSACTION 'tx_for_distributed_commit';-- Transaction is now in prepared state, can survive crashes-- Later: COMMIT PREPARED 'tx_for_distributed_commit';The COMMIT command doesn't actually write data pages to disk—it writes the commit record to the transaction log and ensures the log is on stable storage. Data pages may remain in memory (dirty) and be written later. But because the log contains enough information to redo all changes, durability is guaranteed.
COMMIT Performance Considerations:
COMMIT involves synchronous I/O (fsync) to ensure durability. This has significant performance implications:
- Synchronous commits guarantee durability but add latency (typically 1-10ms per commit depending on storage)
- Group commit optimization batches multiple transactions' log records into a single fsync, amortizing the cost
- Asynchronous commits (PostgreSQL: synchronous_commit=off, SQL Server: DELAYED_DURABILITY) skip the fsync, risking a small window of data loss for faster commits
The ROLLBACK Statement
The ROLLBACK statement undoes all changes made within the current transaction, restoring the database to the state it was in when the transaction began. It's the 'undo everything' button.
Formal Semantics:
When ROLLBACK executes:
- Undo Processing — The database reads undo log records for this transaction and applies them in reverse order, restoring previous values
- Lock Release — All locks held by the transaction are released immediately
- Resource Cleanup — Transaction context and temporary structures are freed
- Log Record — An abort/rollback record is written to the log (for crash recovery)
- Control Return — Application receives confirmation that rollback completed
1234567891011121314151617181920212223242526
-- Basic rollbackBEGIN; UPDATE accounts SET balance = balance - 1000000 WHERE id = 1; -- Oops, that's a million dollars!ROLLBACK;-- Phew, nothing happened -- Equivalent formsROLLBACK WORK;ROLLBACK TRANSACTION;ABORT; -- PostgreSQL alias for ROLLBACK -- Rollback and start new transactionROLLBACK AND CHAIN;-- Equivalent to: ROLLBACK; BEGIN; -- Rollback to savepoint (partial rollback)BEGIN; UPDATE orders SET status = 'processing' WHERE id = 1; SAVEPOINT before_risky; DELETE FROM orders WHERE id = 2; -- Oops, wrong row!ROLLBACK TO SAVEPOINT before_risky; -- orders.id=1 change is preserved -- orders.id=2 delete is undone UPDATE orders SET status = 'confirmed' WHERE id = 1;COMMIT;Unlike COMMIT, ROLLBACK cannot fail in the traditional sense. The database always has enough undo information to reverse a transaction's changes. If the system crashes during rollback, recovery will complete the rollback automatically.
Error States and Transaction Behavior
What happens when an error occurs in the middle of a transaction? The answer varies significantly between database systems, and getting this wrong leads to subtle bugs.
| Database | After Statement Error | Transaction State | Required Action |
|---|---|---|---|
| PostgreSQL | Transaction marked 'aborted' | All subsequent statements fail | Must ROLLBACK before any new work |
| MySQL | Statement rolled back, transaction continues | Still active and usable | Can continue with other statements or COMMIT |
| SQL Server | Depends on XACT_ABORT setting | Varies (see below) | Check XACT_STATE() before deciding |
| Oracle | Statement rolled back, transaction continues | Still active | Can continue, COMMIT, or ROLLBACK |
1234567891011121314151617181920
-- PostgreSQL: Error aborts entire transactionBEGIN; INSERT INTO users (id, name) VALUES (1, 'Alice'); -- OK INSERT INTO users (id, name) VALUES (1, 'Bob'); -- Error: duplicate key -- Transaction is now in 'aborted' state -- This will fail with: "current transaction is aborted" SELECT * FROM users; ROLLBACK; -- Only option: rollback -- Workaround: Use savepointsBEGIN; SAVEPOINT before_insert; INSERT INTO users (id, name) VALUES (1, 'Bob'); -- Might fail -- If it fails, we can recover: ROLLBACK TO SAVEPOINT before_insert; -- Transaction is still usable INSERT INTO users (id, name) VALUES (2, 'Bob'); -- Try with different IDCOMMIT;PostgreSQL's behavior (aborting the transaction on any error) is stricter than other databases. This prevents accidentally committing partial work but requires more careful error handling. Use savepoints for operations that might fail if you need to continue after errors.
Automatic Commit and Rollback
Besides explicit COMMIT and ROLLBACK, databases perform automatic transaction ending in certain situations. Understanding these cases prevents surprises.
- •DDL Statements (MySQL/Oracle) — CREATE, ALTER, DROP cause implicit commit before and after execution. You cannot rollback DDL or the DML that preceded it.
- •Normal Disconnect — Most databases commit pending transactions when a connection closes normally. But don't rely on this—always commit explicitly.
- •Autocommit Mode — Each statement commits immediately after execution.
- •SET AUTOCOMMIT = 1 (MySQL) — Commits any pending transaction when autocommit is enabled.
- •Abnormal Disconnect — If a connection is terminated (network failure, process crash), the database rolls back any uncommitted transaction.
- •Deadlock Resolution — When deadlock is detected, the database chooses a victim transaction and rolls it back automatically.
- •Transaction Timeout — Some configurations enforce maximum transaction duration; exceeded transactions are rolled back.
- •Fatal Errors — Out-of-memory, constraint violations in some systems, or other unrecoverable errors may trigger rollback.
- •Session Termination — Admin terminating a session rolls back its active transaction.
123456789101112131415
-- MySQL: DDL causes implicit commitSTART TRANSACTION; INSERT INTO users (name) VALUES ('Alice'); -- Part of transaction CREATE TABLE temp_log (id INT); -- Implicit COMMIT here! -- The INSERT is now committed, regardless of what follows INSERT INTO users (name) VALUES ('Bob');ROLLBACK; -- Only rolls back Bob, not Alice! -- PostgreSQL: DDL is transactionalBEGIN; INSERT INTO users (name) VALUES ('Alice'); CREATE TABLE temp_log (id INT);ROLLBACK; -- Both INSERT and CREATE are undone!PostgreSQL uniquely supports transactional DDL—CREATE, ALTER, DROP can be part of a transaction and rolled back. This enables powerful deployment patterns like: BEGIN; DROP TABLE old; ALTER TABLE new RENAME TO old; COMMIT; If any step fails, the rollback restores the original state.
Advanced Variations
Beyond basic BEGIN/COMMIT/ROLLBACK, databases offer advanced transaction control features for specialized use cases.
| Feature | Syntax Example | Purpose |
|---|---|---|
| COMMIT AND CHAIN | COMMIT AND CHAIN | Commit and immediately start new transaction with same properties |
| COMMIT WORK RELEASE | COMMIT RELEASE (MySQL) | Commit and disconnect; useful for connection pooling cleanup |
| COMMIT WRITE WAIT | COMMIT WRITE IMMEDIATE WAIT (Oracle) | Control durability vs. performance trade-off |
| PREPARE TRANSACTION | PREPARE TRANSACTION 'id' (PostgreSQL) | First phase of two-phase commit for distributed transactions |
| XA Transactions | XA START, XA PREPARE, XA COMMIT (MySQL) | Cross-database or cross-resource manager transactions |
| DELAYED DURABILITY | BEGIN TRAN WITH DELAYED_DURABILITY=ON (SQL Server) | Trade durability guarantee for performance |
12345678910111213141516171819202122
-- Two-Phase Commit (2PC) for distributed transactions-- Requires max_prepared_transactions > 0 in postgresql.conf -- Phase 1: Prepare on Database ABEGIN; UPDATE accounts SET balance = balance - 100 WHERE id = 1;PREPARE TRANSACTION 'transfer_2024_001';-- Transaction is now 'prepared' - survives crashes -- Meanwhile, on Database B, similar prepare happens... -- Phase 2: Commit both (or rollback both)-- On Database A:COMMIT PREPARED 'transfer_2024_001';-- On Database B:COMMIT PREPARED 'transfer_2024_001'; -- If any prepare fails, rollback all:ROLLBACK PREPARED 'transfer_2024_001'; -- View prepared transactionsSELECT * FROM pg_prepared_xacts;Two-phase commit (2PC) guarantees atomicity across distributed databases but introduces a blocking window where resources are locked waiting for coordinator decision. If the coordinator crashes between prepare and commit, resources may remain locked until manual intervention. Use with care.
Best Practices for Transaction Control
Proper use of BEGIN, COMMIT, and ROLLBACK is essential for reliable applications. Here are battle-tested practices from production systems:
- •Always match BEGIN with COMMIT or ROLLBACK — Every transaction start must have exactly one end. Use try/finally or defer patterns to guarantee this.
- •Prefer explicit over implicit — Even if autocommit is off, explicit BEGIN makes intent clear. Future readers (including yourself) will thank you.
- •Keep transactions as short as possible — Start late, commit early. Never hold transactions open while waiting for user input, network calls, or other external delays.
- •Handle errors with ROLLBACK — Catching an exception without rolling back leaves the database in an uncertain state. Always rollback in error handlers.
- •Test rollback paths — It's easy to only test the happy path. Deliberately trigger errors to verify rollback works correctly.
- •Set appropriate isolation levels — Don't use SERIALIZABLE when READ COMMITTED suffices. Conversely, don't use READ UNCOMMITTED for financial data.
- •Monitor long-running transactions — Set up alerts for transactions exceeding expected duration. They indicate bugs or deadlock/locking issues.
- •Use savepoints sparingly — Savepoints add complexity. Use them when you genuinely need partial rollback, not as a general pattern.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849
// ✅ GOOD: Guaranteed cleanup with try/finally patternasync function transferWithProperHandling( fromId: string, toId: string, amount: number) { const client = await pool.connect(); try { await client.query('BEGIN'); // Validate first const { rows: [sender] } = await client.query( 'SELECT balance FROM accounts WHERE id = $1 FOR UPDATE', [fromId] ); if (sender.balance < amount) { throw new InsufficientFundsError(sender.balance, amount); } // Perform transfer await client.query( 'UPDATE accounts SET balance = balance - $1 WHERE id = $2', [amount, fromId] ); await client.query( 'UPDATE accounts SET balance = balance + $1 WHERE id = $2', [amount, toId] ); await client.query('COMMIT'); } catch (error) { await client.query('ROLLBACK'); throw error; // Re-throw after cleanup } finally { client.release(); // Always release connection }} // ❌ BAD: No error handling, possible leaked transactionasync function transferDangerous(fromId: string, toId: string, amount: number) { await pool.query('BEGIN'); await pool.query(`UPDATE accounts SET balance = balance - ${amount} WHERE id = ${fromId}`); // If this next line throws, transaction remains open! await pool.query(`UPDATE accounts SET balance = balance + ${amount} WHERE id = ${toId}`); await pool.query('COMMIT');}If you don't rollback before releasing a connection to a pool, the next user might inherit your uncommitted transaction. Always ensure transactions are explicitly ended before returning connections.
KEEP LEARNING
There’s more to understand.
You’re reading the introduction. Create a free account to read the complete lesson, save notes and keep track of your progress.