Database Transactions
Learning Objectives
- Understand ACID properties of transactions
- Master transaction control statements (BEGIN, COMMIT, ROLLBACK)
- Implement savepoints for partial rollbacks
- Configure isolation levels and understand their impact
- Handle deadlocks and lock timeouts
- Use transactions in stored procedures
- Apply transactions in PHP applications
- Optimize transaction performance
Understanding Database Transactions
Transactions ensure data integrity by treating multiple operations as a single unit of work - either all succeed or all fail! 🔒
Transaction Key Concepts
Transaction Control Statements
graph TB
START[Start] --> AUTO[Autocommit ON]
START --> MANUAL[Autocommit OFF]
AUTO --> SINGLE[Each statement
auto-commits] MANUAL --> BEGIN[BEGIN/START TRANSACTION] BEGIN --> OPS[Operations] OPS --> DECISION{Success?} DECISION -->|Yes| COMMIT[COMMIT] DECISION -->|No| ROLLBACK[ROLLBACK] OPS --> SAVE[SAVEPOINT] SAVE --> MORE[More Operations] MORE --> PARTIAL[ROLLBACK TO SAVEPOINT] style BEGIN fill:#dcfce7 style COMMIT fill:#dbeafe style ROLLBACK fill:#fee2e2 style SAVE fill:#fef3c7
auto-commits] MANUAL --> BEGIN[BEGIN/START TRANSACTION] BEGIN --> OPS[Operations] OPS --> DECISION{Success?} DECISION -->|Yes| COMMIT[COMMIT] DECISION -->|No| ROLLBACK[ROLLBACK] OPS --> SAVE[SAVEPOINT] SAVE --> MORE[More Operations] MORE --> PARTIAL[ROLLBACK TO SAVEPOINT] style BEGIN fill:#dcfce7 style COMMIT fill:#dbeafe style ROLLBACK fill:#fee2e2 style SAVE fill:#fef3c7
Transaction Examples
-- ========================================
-- BASIC TRANSACTION CONTROL
-- ========================================
-- Check autocommit status
SHOW VARIABLES LIKE 'autocommit';
-- Disable autocommit
SET autocommit = 0;
-- Simple transaction
START TRANSACTION; -- or BEGIN
-- Operations
INSERT INTO accounts (account_id, balance) VALUES (1, 1000);
UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
UPDATE accounts SET balance = balance + 100 WHERE account_id = 2;
-- Commit changes
COMMIT;
-- Or rollback if error
ROLLBACK;
-- ========================================
-- TRANSACTION WITH ERROR HANDLING
-- ========================================
-- Bank transfer example
START TRANSACTION;
-- Debit from account
UPDATE accounts
SET balance = balance - 500
WHERE account_id = 1;
-- Check if balance went negative
SELECT balance INTO @balance
FROM accounts
WHERE account_id = 1;
IF @balance < 0 THEN
ROLLBACK;
SELECT 'Insufficient funds' AS error_message;
ELSE
-- Credit to account
UPDATE accounts
SET balance = balance + 500
WHERE account_id = 2;
COMMIT;
SELECT 'Transfer successful' AS message;
END IF;
-- ========================================
-- SAVEPOINTS
-- ========================================
START TRANSACTION;
-- First operation
INSERT INTO orders (customer_id, order_date)
VALUES (123, NOW());
SET @order_id = LAST_INSERT_ID();
SAVEPOINT order_created;
-- Add order items
INSERT INTO order_items (order_id, product_id, quantity)
VALUES (@order_id, 1, 5);
SAVEPOINT item1_added;
INSERT INTO order_items (order_id, product_id, quantity)
VALUES (@order_id, 2, 3);
-- Check inventory
SELECT quantity_available INTO @stock
FROM inventory
WHERE product_id = 2;
IF @stock < 3 THEN
-- Rollback only the second item
ROLLBACK TO SAVEPOINT item1_added;
SELECT 'Product 2 out of stock, removed from order' AS message;
END IF;
-- Commit the transaction
COMMIT;
-- ========================================
-- READ-ONLY TRANSACTIONS
-- ========================================
-- Optimize read-only operations
START TRANSACTION READ ONLY;
SELECT * FROM large_table WHERE conditions;
-- Multiple SELECT statements
-- No locks for writes
COMMIT; -- or ROLLBACK (same effect for read-only)
-- ========================================
-- TRANSACTION WITH LOCKS
-- ========================================
START TRANSACTION;
-- Lock rows for update
SELECT * FROM inventory
WHERE product_id = 123
FOR UPDATE;
-- Check and update
UPDATE inventory
SET quantity = quantity - 10
WHERE product_id = 123;
COMMIT;
-- Share mode lock (allows reads)
START TRANSACTION;
SELECT * FROM products
WHERE category = 'Electronics'
LOCK IN SHARE MODE;
-- Other transactions can read but not write
COMMIT;
Isolation Levels
Working with Isolation Levels
-- ========================================
-- ISOLATION LEVEL CONFIGURATION
-- ========================================
-- Check current isolation level
SELECT @@GLOBAL.transaction_isolation;
SELECT @@SESSION.transaction_isolation;
-- Set session isolation level
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
-- Set for next transaction only
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
-- ========================================
-- DEMONSTRATING ISOLATION PROBLEMS
-- ========================================
-- DIRTY READ (READ UNCOMMITTED)
-- Session 1:
START TRANSACTION;
UPDATE products SET price = 999 WHERE product_id = 1;
-- Don't commit yet
-- Session 2:
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
START TRANSACTION;
SELECT price FROM products WHERE product_id = 1; -- Sees 999 (dirty)
COMMIT;
-- Session 1:
ROLLBACK; -- Price reverts, but Session 2 saw wrong value
-- ========================================
-- NON-REPEATABLE READ
-- ========================================
-- Session 1:
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;
SELECT price FROM products WHERE product_id = 1; -- Returns 100
-- Session 2:
UPDATE products SET price = 150 WHERE product_id = 1;
COMMIT;
-- Session 1 (same transaction):
SELECT price FROM products WHERE product_id = 1; -- Returns 150 (changed!)
COMMIT;
-- ========================================
-- PHANTOM READ
-- ========================================
-- Session 1:
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION;
SELECT COUNT(*) FROM products WHERE price > 100; -- Returns 5
-- Session 2:
INSERT INTO products (name, price) VALUES ('New', 200);
COMMIT;
-- Session 1 (same transaction):
SELECT COUNT(*) FROM products WHERE price > 100; -- Still 5 (no phantom)
-- But in READ COMMITTED, would see 6
-- ========================================
-- DEADLOCK HANDLING
-- ========================================
-- Session 1:
START TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- Wait
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
-- Session 2:
START TRANSACTION;
UPDATE accounts SET balance = balance - 50 WHERE id = 2;
-- Wait
UPDATE accounts SET balance = balance + 50 WHERE id = 1;
-- DEADLOCK! One transaction will be rolled back
-- Handling in application:
START TRANSACTION;
-- Set lock timeout
SET innodb_lock_wait_timeout = 5;
-- Attempt operation
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- Check for lock timeout error
GET DIAGNOSTICS CONDITION 1
@errno = MYSQL_ERRNO;
IF @errno = 1205 THEN -- Lock timeout
ROLLBACK;
-- Retry or handle error
ELSE
COMMIT;
END IF;
Transactions in Stored Procedures
Advanced Transaction Patterns
-- ========================================
-- STORED PROCEDURE WITH TRANSACTIONS
-- ========================================
DELIMITER $$
CREATE PROCEDURE transfer_funds(
IN from_account INT,
IN to_account INT,
IN amount DECIMAL(10,2),
OUT result VARCHAR(100)
)
BEGIN
DECLARE exit handler FOR SQLEXCEPTION
BEGIN
ROLLBACK;
SET result = 'Transaction failed - rolled back';
END;
DECLARE exit handler FOR SQLWARNING
BEGIN
ROLLBACK;
SET result = 'Transaction warning - rolled back';
END;
START TRANSACTION;
-- Check source account balance
SELECT balance INTO @current_balance
FROM accounts
WHERE account_id = from_account
FOR UPDATE; -- Lock the row
IF @current_balance < amount THEN
ROLLBACK;
SET result = 'Insufficient funds';
ELSE
-- Debit source account
UPDATE accounts
SET balance = balance - amount,
last_transaction = NOW()
WHERE account_id = from_account;
-- Credit destination account
UPDATE accounts
SET balance = balance + amount,
last_transaction = NOW()
WHERE account_id = to_account;
-- Log transaction
INSERT INTO transaction_log (
from_account, to_account, amount,
transaction_date, status
) VALUES (
from_account, to_account, amount,
NOW(), 'completed'
);
COMMIT;
SET result = 'Transfer successful';
END IF;
END$$
DELIMITER ;
-- ========================================
-- NESTED TRANSACTIONS (Emulated)
-- ========================================
DELIMITER $$
CREATE PROCEDURE process_order(
IN p_customer_id INT,
IN p_items JSON,
OUT p_order_id INT
)
BEGIN
DECLARE v_done INT DEFAULT 0;
DECLARE v_product_id INT;
DECLARE v_quantity INT;
DECLARE v_idx INT DEFAULT 0;
DECLARE exit handler FOR SQLEXCEPTION
BEGIN
ROLLBACK;
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Order processing failed';
END;
START TRANSACTION;
-- Create order
INSERT INTO orders (customer_id, order_date, status)
VALUES (p_customer_id, NOW(), 'pending');
SET p_order_id = LAST_INSERT_ID();
-- Create savepoint for items
SAVEPOINT before_items;
-- Process each item
WHILE v_idx < JSON_LENGTH(p_items) DO
SET v_product_id = JSON_EXTRACT(p_items, CONCAT('$[', v_idx, '].product_id'));
SET v_quantity = JSON_EXTRACT(p_items, CONCAT('$[', v_idx, '].quantity'));
-- Check inventory
SELECT quantity_available INTO @stock
FROM inventory
WHERE product_id = v_product_id
FOR UPDATE;
IF @stock < v_quantity THEN
-- Rollback to savepoint if item unavailable
ROLLBACK TO SAVEPOINT before_items;
UPDATE orders
SET status = 'partial',
notes = CONCAT('Product ', v_product_id, ' insufficient stock')
WHERE order_id = p_order_id;
ELSE
-- Add order item
INSERT INTO order_items (order_id, product_id, quantity)
VALUES (p_order_id, v_product_id, v_quantity);
-- Update inventory
UPDATE inventory
SET quantity_available = quantity_available - v_quantity
WHERE product_id = v_product_id;
END IF;
SET v_idx = v_idx + 1;
END WHILE;
COMMIT;
END$$
DELIMITER ;
-- ========================================
-- DISTRIBUTED TRANSACTION EXAMPLE
-- ========================================
-- XA transactions for distributed systems
XA START 'xatest';
INSERT INTO local_table VALUES (1, 'data');
XA END 'xatest';
XA PREPARE 'xatest';
XA COMMIT 'xatest';
-- Or rollback
XA ROLLBACK 'xatest';
-- Recovery after crash
XA RECOVER;
Practice Exercise
Transaction Challenges