Transaction Management

Transaction Management in MySQLi
Transaction management is a crucial aspect of database operations, ensuring that a series of database operations are executed as a single unit of work. This concept is especially important in maintaining data integrity and consistency, especially in scenarios involving multiple interdependent operations.
In this article, we will delve into transaction management in MySQLi, covering the fundamentals, essential commands, and practical examples to help you effectively manage transactions in your PHP applications.

Understanding Transactions

A transaction in a database system is a sequence of operations performed as a single logical unit of work. A transaction must adhere to the ACID properties to ensure data integrity:
  1. Atomicity: Ensures that all operations within the transaction are completed successfully. If any operation fails, the entire transaction is aborted.
  2. Consistency: Ensures that the database transitions from one valid state to another.
  3. Isolation: Ensures that transactions are isolated from each other until they are completed.
  4. Durability: Ensures that once a transaction is committed, it is permanently recorded in the database.

Key Transaction Control Commands in MySQLi

BEGIN

The BEGIN command initiates a new transaction. In MySQLi, you can start a transaction using the begin_transaction method or the SQL command START TRANSACTION.

COMMIT

The COMMIT command saves all changes made during the transaction to the database. Once committed, the changes become permanent and visible to other transactions.

ROLLBACK

The ROLLBACK command undoes all changes made during the transaction. This is useful for reverting the database to its previous state in case of errors or inconsistencies.

Using Transactions in MySQLi with PHP

To illustrate the use of transactions in MySQLi, let’s walk through a practical example involving a simple banking system. We’ll create a PHP script that handles money transfers between accounts, ensuring that all operations are performed atomically.

Setting Up the Database

First, set up a MySQL database and create a table for storing account information. We’ll use an accounts table with fields for id, name, and balance.
				
					CREATE DATABASE bank;
USE bank;

CREATE TABLE accounts (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(50),
    balance DECIMAL(10, 2)
);

INSERT INTO accounts (name, balance) VALUES ('Alice', 1000.00);
INSERT INTO accounts (name, balance) VALUES ('Bob', 1000.00);

				
			

PHP Script for Transaction Management

We’ll write a PHP script that transfers money from one account to another, ensuring that the transaction is atomic. If any part of the transaction fails, it will be rolled back to maintain data consistency.

				
					<?php
$servername = "localhost";
$username = "root";
$password = "";
$dbname = "bank";

// Create connection
$conn = new mysqli($servername, $username, $password, $dbname);

// Check connection
if ($conn->connect_error) {
    die("Connection failed: " . $conn->connect_error);
}

function transferMoney($fromAccount, $toAccount, $amount) {
    global $conn;

    // Begin transaction
    $conn->begin_transaction();

    try {
        // Deduct amount from sender's account
        $sql = "UPDATE accounts SET balance = balance - ? WHERE id = ?";
        $stmt = $conn->prepare($sql);
        $stmt->bind_param("di", $amount, $fromAccount);
        $stmt->execute();
        if ($stmt->affected_rows === 0) {
            throw new Exception("Sender's account not found or insufficient funds.");
        }

        // Add amount to receiver's account
        $sql = "UPDATE accounts SET balance = balance + ? WHERE id = ?";
        $stmt = $conn->prepare($sql);
        $stmt->bind_param("di", $amount, $toAccount);
        $stmt->execute();
        if ($stmt->affected_rows === 0) {
            throw new Exception("Receiver's account not found.");
        }

        // Commit transaction
        $conn->commit();
        echo "Transaction successful!";
    } catch (Exception $e) {
        // Rollback transaction on error
        $conn->rollback();
        echo "Transaction failed: " . $e->getMessage();
    }

    $stmt->close();
}

// Example usage
transferMoney(1, 2, 100.00);

$conn->close();
?>

				
			

Explanation

  1. Connecting to the Database: Establish a connection to the MySQL database using MySQLi.
  2. Starting the Transaction: Use $conn->begin_transaction() to initiate a new transaction.
  3. Executing SQL Statements:
    • Deduct the amount from the sender’s account.
    • Add the amount to the receiver’s account.
  4. Error Handling: If any SQL operation fails, an exception is thrown.
  5. Committing the Transaction: If all operations succeed, $conn->commit() is called to save the changes.
  6. Rolling Back the Transaction: If an exception is caught, $conn->rollback() is called to revert the changes.

Advanced Transaction Management

In more complex scenarios, you might need to handle nested transactions or savepoints. MySQLi supports these advanced transaction control features as well.

Nested Transactions and Savepoints

Savepoints allow you to set intermediate points within a transaction to which you can roll back without affecting the entire transaction. This is useful for partial error recovery.
				
					<?php
$servername = "localhost";
$username = "root";
$password = "";
$dbname = "bank";

// Create connection
$conn = new mysqli($servername, $username, $password, $dbname);

// Check connection
if ($conn->connect_error) {
    die("Connection failed: " . $conn->connect_error);
}

function transferMoneyWithSavepoint($fromAccount, $toAccount, $amount) {
    global $conn;

    // Begin transaction
    $conn->begin_transaction();

    try {
        // Deduct amount from sender's account
        $sql = "UPDATE accounts SET balance = balance - ? WHERE id = ?";
        $stmt = $conn->prepare($sql);
        $stmt->bind_param("di", $amount, $fromAccount);
        $stmt->execute();
        if ($stmt->affected_rows === 0) {
            throw new Exception("Sender's account not found or insufficient funds.");
        }

        // Set savepoint
        $conn->savepoint("deducted");

        // Add amount to receiver's account
        $sql = "UPDATE accounts SET balance = balance + ? WHERE id = ?";
        $stmt = $conn->prepare($sql);
        $stmt->bind_param("di", $amount, $toAccount);
        $stmt->execute();
        if ($stmt->affected_rows === 0) {
            // Rollback to savepoint if receiver's account is not found
            $conn->rollback_to("deducted");
            throw new Exception("Receiver's account not found. Rolled back to deduct step.");
        }

        // Commit transaction
        $conn->commit();
        echo "Transaction successful!";
    } catch (Exception $e) {
        // Rollback transaction on error
        $conn->rollback();
        echo "Transaction failed: " . $e->getMessage();
    }

    $stmt->close();
}

// Example usage
transferMoneyWithSavepoint(1, 3, 100.00);

$conn->close();
?>

				
			

Explanation of Savepoints

  1. Setting a Savepoint: Use $conn->savepoint(“deducted”) to set a savepoint after deducting the amount from the sender’s account.
  2. Rolling Back to Savepoint: If the receiver’s account is not found, roll back to the “deducted” savepoint using $conn->rollback_to(“deducted”).
This approach allows partial rollback within a transaction, providing finer control over error recovery.

Handling Concurrency with Transactions

In a multi-user environment, concurrent transactions can lead to issues such as dirty reads, non-repeatable reads, and phantom reads. MySQL provides different isolation levels to handle these concurrency issues:
  1. READ UNCOMMITTED: Allows dirty reads.
  2. READ COMMITTED: Prevents dirty reads but allows non-repeatable reads.
  3. REPEATABLE READ: Prevents dirty and non-repeatable reads but allows phantom reads.
  4. SERIALIZABLE: Prevents dirty, non-repeatable, and phantom reads.

Setting Isolation Levels in MySQLi

You can set the isolation level for a transaction in MySQLi using the SET TRANSACTION ISOLATION LEVEL statement.
				
					<?php
$servername = "localhost";
$username = "root";
$password = "";
$dbname = "bank";

// Create connection
$conn = new mysqli($servername, $username, $password, $dbname);

// Check connection
if ($conn->connect_error) {
    die("Connection failed: " . $conn->connect_error);
}

function setIsolationLevel($level) {
    global $conn;

    $sql = "SET TRANSACTION ISOLATION LEVEL $level";
    if (!$conn->query($sql)) {
        throw new Exception("Failed to set isolation level: " . $conn->error);
    }
}

// Example usage
try {
    setIsolationLevel('REPEATABLE READ');
    echo "Isolation level set successfully!";
} catch (Exception $e) {
    echo $e->getMessage();
}

$conn->close();
?>

				
			

Explanation of Isolation Level Setting

  1. Setting Isolation Level: Use the SET TRANSACTION ISOLATION LEVEL statement to set the desired isolation level.
  2. Error Handling: If setting the isolation level fails, an exception is thrown.

Conclusion

Transaction management is a critical aspect of database operations, ensuring that a series of operations are executed atomically and consistently. By understanding and using transaction control commands such as BEGIN, COMMIT, and ROLLBACK, you can maintain data integrity in your MySQLi applications. Advanced features like savepoints and isolation levels provide additional control and flexibility in managing complex transactions and handling concurrency issues. Implementing effective transaction management in your applications will significantly enhance the reliability and robustness of your database operations, ensuring that your data remains consistent and accurate even in the face of errors or concurrent access.
Scroll to Top