ネストされたトランザクションも怖くない!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]);
}