Skip to main content

Course Progress

Loading...

Database Transactions

Duration: 80 minutes
Module 3: Database Objects

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
  • Atomicity:All operations succeed or all fail
  • Consistency:Database remains in valid state
  • Isolation:Concurrent transactions don't interfere
  • Durability:Committed changes persist
  • Transactions prevent partial updates and maintain data integrity
  • Essential for financial operations and critical data

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

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
Implement these transaction scenarios:
  1. Create a banking system with atomic transfers
  2. Implement an order processing system with inventory checks
  3. Build a booking system preventing double-bookings
  4. Design a points/rewards system with transaction history
  5. Create a batch processing system with savepoints
  6. Implement deadlock detection and retry logic
  7. Build an audit system tracking all changes
  8. Design a two-phase commit simulation