ネストされたトランザクションも怖くない!PDO::beginTransactionで複雑なデータベース操作を制覇


PDO::beginTransaction の動作を理解するには、まず オートコミットモード について理解する必要があります。オートコミットモードがオンの場合、PDO は実行された各 SQL ステートメントを自動的にコミットします。つまり、ステートメントが成功すると、データベースへの変更が永続的に保存されます。一方、PDO::beginTransaction を呼び出すと、オートコミットモードがオフになり、トランザクション境界が確立されます。この境界内で行われたすべての SQL ステートメントは、PDO::commit を呼び出すまでコミットされません。

PDO::beginTransaction の利点は次のとおりです。

  • パフォーマンスの向上: 複数の SQL ステートメントを単一のトランザクションにグループ化することで、データベースへのラウンドトリップを削減し、パフォーマンスを向上させることができます。
  • 同時実行性の制御: トランザクション中は、他のユーザーによるデータの変更がブロックされるため、競合状態を回避できます。
  • データ整合性の保証: エラーが発生した場合、トランザクション全体をロールバックすることで、データベースの一貫性を保つことができます。

PDO::beginTransaction の使用方法の例を次に示します。

<?php

$db = new PDO('mysql:host=localhost;dbname=test', 'username', 'password');

try {
    $db->beginTransaction();

    // トランザクション内の SQL ステートメントを実行

    $db->exec('UPDATE users SET name = "John Doe" WHERE id = 1');
    $db->exec('INSERT INTO orders (user_id, product_id) VALUES (1, 123)');

    $db->commit();
    echo "トランザクションが正常にコミットされました。";
} catch (PDOException $e) {
    $db->rollBack();
    echo "トランザクションのロールバックに失敗しました: " . $e->getMessage();
}

この例では、PDO::beginTransaction を使用してトランザクションを開始し、ユーザーの名前を更新し、新しい注文を挿入する 2 つの SQL ステートメントを実行します。PDO::commit を呼び出すことで、これらの変更がデータベースに永続的に保存されます。エラーが発生した場合、PDO::rollBack を呼び出してトランザクションをロールバックし、データベースへの変更を元に戻します。

PDO::beginTransaction は、データベース操作の整合性と信頼性を保証するために不可欠なツールです。トランザクションが必要な複雑なデータベース操作を実行する場合は、PDO::beginTransaction を使用することを強くお勧めします。

  • 一部のデータベースでは、トランザクションのサポートに制限がある場合があります。
  • ネストされたトランザクションはサポートされていません。
  • PDO::beginTransaction を呼び出す前に、データベース接続が開いていることを確認してください。


<?php

$db = new PDO('mysql:host=localhost;dbname=test', 'username', 'password');

try {
    $db->beginTransaction();

    // ユーザー情報を更新
    $userId = 1;
    $newUserName = "John Doe";
    $sql = "UPDATE users SET name = :name WHERE id = :id";
    $stmt = $db->prepare($sql);
    $stmt->bindParam(':id', $userId);
    $stmt->bindParam(':name', $newUserName);
    $stmt->execute();

    // 新しい注文を挿入
    $productId = 123;
    $sql = "INSERT INTO orders (user_id, product_id) VALUES (:user_id, :product_id)";
    $stmt = $db->prepare($sql);
    $stmt->bindParam(':user_id', $userId);
    $stmt->bindParam(':product_id', $productId);
    $stmt->execute();

    $db->commit();
    echo "トランザクションが正常にコミットされました。";
} catch (PDOException $e) {
    $db->rollBack();
    echo "トランザクションのロールバックに失敗しました: " . $e->getMessage();
}

このコードでは、まず $userId$newUserName 変数を定義して、ユーザー情報を更新するために使用します。次に、UPDATE ステートメントを含む $sql 変数を定義し、ユーザーの名前を更新するために使用します。

PDO::prepare メソッドを使用して、$sql ステートメントを準備し、$stmt 変数に格納します。次に、bindParam メソッドを使用して、ステートメントのパラメーター :id:name$userId$newUserName 変数にバインドします。最後に、execute メソッドを呼び出してステートメントを実行します。

新しい注文を挿入するために、同様のプロセスを繰り返します。まず、$productId 変数を定義して、注文に使用する製品 ID を指定します。次に、INSERT ステートメントを含む $sql 変数を定義し、新しい注文を挿入するために使用します。

PDO::prepare メソッドを使用して、$sql ステートメントを準備し、$stmt 変数に格納します。次に、bindParam メソッドを使用して、ステートメントのパラメーター :user_id:product_id$userId$productId 変数にバインドします。最後に、execute メソッドを呼び出してステートメントを実行します。

すべてのステートメントが成功したら、PDO::commit メソッドを呼び出してトランザクションをコミットします。エラーが発生した場合は、PDO::rollBack メソッドを呼び出してトランザクションをロールバックし、データベースへの変更を元に戻します。

以下の追加例は、PDO::beginTransaction を使用して、エラー処理とロックを実装する方法を示しています。

<?php

$db = new PDO('mysql:host=localhost;dbname=test', 'username', 'password');

try {
    $db->beginTransaction();

    // ユーザー情報を更新
    $userId = 1;
    $newUserName = "John Doe";
    $sql = "UPDATE users SET name = :name WHERE id = :id";
    $stmt = $db->prepare($sql);
    $stmt->bindParam(':id', $userId);
    $stmt->bindParam(':name', $newUserName);

    if (!$stmt->execute()) {
        throw new PDOException("ユーザー情報の更新に失敗しました: " . $stmt->errorInfo()[2]);
    }

    // 新しい注文を挿入
    $productId = 123;
    $sql = "INSERT INTO orders (user_id, product_id) VALUES (:user_id, :product_id)";
    $stmt = $db->prepare($sql);
    $stmt->bindParam(':user_id', $userId);
    $stmt->bindParam(':product_id', $productId);

    if (!$stmt->execute()) {
        throw new PDOException("新しい注文の挿入に失敗しました


SAVEPOINT を使用する

SAVEPOINT は、トランザクション内でチェックポイント を作成する機能です。SAVEPOINT を使用すると、トランザクションの一部をコミットしたりロールバックしたりすることができ、よりきめ細かなトランザクション制御が可能になります。

SAVEPOINT を使用する例:

<?php

$db = new PDO('mysql:host=localhost;dbname=test', 'username', 'password');

try {
    $db->beginTransaction();

    // ユーザー情報を更新
    $userId = 1;
    $newUserName = "John Doe";
    $sql = "UPDATE users SET name = :name WHERE id = :id";
    $stmt = $db->prepare($sql);
    $stmt->bindParam(':id', $userId);
    $stmt->bindParam(':name', $newUserName);

    if (!$stmt->execute()) {
        $db->rollbackToSavepoint('user_update');
        throw new PDOException("ユーザー情報の更新に失敗しました: " . $stmt->errorInfo()[2]);
    }

    // SAVEPOINT を作成
    $db->exec('SAVEPOINT user_update');

    // 新しい注文を挿入
    $productId = 123;
    $sql = "INSERT INTO orders (user_id, product_id) VALUES (:user_id, :product_id)";
    $stmt = $db->prepare($sql);
    $stmt->bindParam(':user_id', $userId);
    $stmt->bindParam(':product_id', $productId);

    if (!$stmt->execute()) {
        $db->rollbackToSavepoint('user_update');
        throw new PDOException("新しい注文の挿入に失敗しました: " . $stmt->errorInfo()[2]);
    }

    // ユーザー情報と注文の更新をコミット
    $db->commit();
    echo "トランザクションが正常にコミットされました。";
} catch (PDOException $e) {
    $db->rollback();
    echo "トランザクションのロールバックに失敗しました: " . $e->getMessage();
}

この例では、SAVEPOINT user_update を作成して、ユーザー情報の更新をコミットする前にチェックポイントを設定します。新しい注文の挿入に失敗した場合、rollbackToSavepoint('user_update') を呼び出して、ユーザー情報の更新のみをロールバックし、元の状態に戻します。

ネストされたトランザクションを使用する

ネストされたトランザクション は、親トランザクション内に子トランザクションを作成する機能です。子トランザクションは、親トランザクションから独立してコミットまたはロールバックできます。

ネストされたトランザクション を使用する例:

<?php

$db = new PDO('mysql:host=localhost;dbname=test', 'username', 'password');

try {
    $db->beginTransaction(); // 親トランザクションを開始

    // ユーザー情報を更新するネストされたトランザクション
    try {
        $db->beginTransaction(); // 子トランザクションを開始

        $userId = 1;
        $newUserName = "John Doe";
        $sql = "UPDATE users SET name = :name WHERE id = :id";
        $stmt = $db->prepare($sql);
        $stmt->bindParam(':id', $userId);
        $stmt->bindParam(':name', $newUserName);

        if (!$stmt->execute()) {
            throw new PDOException("ユーザー情報の更新に失敗しました: " . $stmt->errorInfo()[2]);
        }

        $db->commit(); // 子トランザクションをコミット
    } catch (PDOException $e) {
        $db->rollback(); // 子トランザクションをロールバック
        throw new PDOException("ユーザー情報の更新に失敗しました: " . $e->getMessage());
    }

    // 新しい注文を挿入するネストされたトランザクション
    try {
        $db->beginTransaction(); // 子トランザクションを開始

        $productId = 123;
        $sql = "INSERT INTO orders (user_id, product_id) VALUES (:user_id, :product_id)";
        $stmt = $db->prepare($sql);
        $stmt->bindParam(':user_id', $userId);
        $stmt->bindParam(':product_id', $productId);

        if (!$stmt->execute()) {
            throw new PDOException("新しい注文の挿入に失敗しました: " . $stmt->errorInfo()[2]);
        }