A transaction is a logical unit of work comprising one or more SQL statements. TCL (Transaction Control Language) manages transaction boundaries: BEGIN starts a transaction, COMMIT permanently saves all changes, ROLLBACK undoes all changes since the last COMMIT, and SAVEPOINT marks a partial rollback point within a transaction. Transactions enforce ACID properties — ensuring data integrity even when multiple users run concurrent operations or the system crashes mid-operation.
Real-life analogy: The online order
Placing an order online involves: deduct from inventory, create order record, charge payment, send confirmation email. All four must succeed or none should happen. If payment fails after inventory is deducted, the item is gone but not paid for — inconsistent state. A database transaction wraps all four operations: if any fails, ROLLBACK restores everything to before the transaction started. COMMIT only happens when all four succeed.
BEGIN, COMMIT, ROLLBACK
Transaction control with error handling
-- Basic transaction structure
BEGIN; -- Start transaction (PostgreSQL)
-- or: START TRANSACTION; -- MySQL/SQL Server
UPDATE Inventory SET Stock = Stock - 1 WHERE ProductID = 42 AND Stock > 0;
-- Check if update affected a row (stock was available)
-- If not, we should rollback
INSERT INTO Orders (CustomerID, ProductID, Quantity, Amount)
VALUES (101, 42, 1, 299.99);
UPDATE Account SET Balance = Balance - 299.99
WHERE AccountID = 'CUST101' AND Balance >= 299.99;
COMMIT; -- All changes become permanent
-- If any statement fails, the DBMS auto-rollbacks (in most modes)
-- Explicit ROLLBACK on error (PL/pgSQL stored procedure)
CREATE OR REPLACE PROCEDURE place_order(
p_customer_id INT, p_product_id INT, p_qty INT
) LANGUAGE plpgsql AS $$
DECLARE
v_stock INT;
v_price DECIMAL(10,2);
v_balance DECIMAL(12,2);
BEGIN
-- Check stock
SELECT Stock INTO v_stock FROM Inventory WHERE ProductID = p_product_id;
IF v_stock < p_qty THEN
RAISE EXCEPTION 'Insufficient stock: % available', v_stock;
END IF;
-- Deduct stock
UPDATE Inventory SET Stock = Stock - p_qty WHERE ProductID = p_product_id;
-- Get price
SELECT Price INTO v_price FROM Product WHERE ProductID = p_product_id;
-- Create order
INSERT INTO Orders (CustomerID, ProductID, Quantity, Amount)
VALUES (p_customer_id, p_product_id, p_qty, v_price * p_qty);
-- Charge account
UPDATE Account SET Balance = Balance - (v_price * p_qty)
WHERE CustomerID = p_customer_id;
COMMIT;
RAISE NOTICE 'Order placed successfully';
EXCEPTION WHEN OTHERS THEN
ROLLBACK;
RAISE EXCEPTION 'Order failed: %', SQLERRM;
END;
$$;
-- Autocommit mode (default in most SQL clients)
-- Without explicit BEGIN/COMMIT, each statement is its own transaction
UPDATE Employee SET Salary = 50000 WHERE EmpID = 1; -- Auto-committed immediately
-- No ROLLBACK possible after this!SAVEPOINT — partial rollback
SAVEPOINT for fine-grained rollback control
-- SAVEPOINT marks a point within a transaction to roll back to
-- Without losing ALL work done since BEGIN
BEGIN;
-- Step 1: Update department budgets
UPDATE Department SET Budget = Budget * 1.10;
SAVEPOINT after_budget_update; -- Mark this point
-- Step 2: Give raises to all active employees
UPDATE Employee SET Salary = Salary * 1.05 WHERE Status = 'Active';
SAVEPOINT after_salary_update;
-- Step 3: Calculate and insert bonus records (this might fail)
INSERT INTO Bonus (EmpID, Amount, Year)
SELECT EmpID, Salary * 0.10, 2024 FROM Employee WHERE Status = 'Active';
-- If bonus calculation has an error, rollback ONLY to after_salary_update
-- (undoes the INSERT but keeps the salary UPDATE)
ROLLBACK TO SAVEPOINT after_salary_update;
-- Try a different bonus calculation
INSERT INTO Bonus (EmpID, Amount, Year)
SELECT EmpID, LEAST(Salary * 0.10, 50000), 2024 FROM Employee WHERE Status = 'Active';
COMMIT; -- Saves: budget update + salary update + corrected bonus insert
-- RELEASE SAVEPOINT: remove a savepoint (free memory, cannot rollback to it anymore)
RELEASE SAVEPOINT after_budget_update;
-- Nested transactions (PostgreSQL uses savepoints internally)
-- Some DBMS support nested BEGIN...COMMIT within a transaction
-- PostgreSQL does NOT support true nested transactions — use SAVEPOINTs insteadTransaction isolation levels
| Isolation Level | Dirty Read | Non-repeatable Read | Phantom Read | Use case |
|---|---|---|---|---|
| READ UNCOMMITTED | Possible | Possible | Possible | Rarely used — analytics where exact consistency not critical |
| READ COMMITTED | Prevented | Possible | Possible | Default for PostgreSQL, Oracle — good balance for most apps |
| REPEATABLE READ | Prevented | Prevented | Possible | Default for MySQL InnoDB — financial calculations in one transaction |
| SERIALIZABLE | Prevented | Prevented | Prevented | Banking, stock trading — strictest, most locking overhead |
Setting transaction isolation level
-- Set for the current transaction (PostgreSQL/SQL Server)
BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
-- or:
SET TRANSACTION ISOLATION LEVEL READ COMMITTED; -- SQL Server
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; -- MySQL
-- PostgreSQL: set default isolation level for session
SET default_transaction_isolation = 'serializable';
-- Check current isolation level
SHOW default_transaction_isolation; -- PostgreSQL
SELECT @@TX_ISOLATION; -- MySQL
-- Deadlock example and handling:
-- T1: UPDATE Account SET Balance = Balance - 100 WHERE ID = 1;
-- T1: UPDATE Account SET Balance = Balance + 100 WHERE ID = 2;
-- T2: UPDATE Account SET Balance = Balance - 50 WHERE ID = 2; -- Waits for T1
-- T2: UPDATE Account SET Balance = Balance + 50 WHERE ID = 1; -- T1 waits for T2 → DEADLOCK!
-- DBMS detects cycle and aborts the younger transaction with error:
-- ERROR: deadlock detected — DETAIL: Process 12345 waits for T2; Process 12346 waits for T1
-- Handle in application: catch deadlock error and retry the transactionPractice questions
- What is autocommit mode and why is it dangerous for multi-step operations? (Answer: In autocommit mode, each SQL statement is automatically committed as its own transaction. For multi-step operations (deduct inventory + create order), if the second step fails, the first is already permanently committed — leaving data inconsistent. Always use explicit BEGIN/COMMIT for multi-step operations.)
- ROLLBACK TO SAVEPOINT sp1 vs ROLLBACK — what is the difference? (Answer: ROLLBACK TO SAVEPOINT sp1: undoes changes back to the savepoint but keeps the transaction open — you can continue and commit the work done before sp1. ROLLBACK: undoes ALL changes since BEGIN and ends the transaction.)
- Why would you choose READ COMMITTED isolation level over SERIALIZABLE? (Answer: SERIALIZABLE prevents all concurrency anomalies but requires the most locking — reducing throughput for high-concurrency systems. READ COMMITTED has less locking and is usually sufficient for most OLTP applications where phantom reads are acceptable.)
- What is a deadlock and how does the DBMS resolve it? (Answer: Deadlock = circular wait: T1 holds lock on A waiting for B; T2 holds B waiting for A. Neither can proceed. DBMS detects the cycle and aborts one transaction (the victim — usually the one that has done less work or is cheapest to redo), allowing the other to complete.)
- In PostgreSQL, how do you implement nested transactions? (Answer: PostgreSQL does not support true nested transactions. Use SAVEPOINTs instead — SAVEPOINT sp1 within an open transaction acts like a nested transaction start point, and ROLLBACK TO SAVEPOINT sp1 acts like a nested rollback.)
On LumiChats
LumiChats can generate complete transaction-safe stored procedures for complex business operations, explain ACID violations with examples, and help diagnose deadlock scenarios by analysing your lock dependency chains.
Try it free