第11回: PDOトランザクション — 「途中で止まっても大丈夫」な処理の作り方

章: 第2章: データベース応用

振込処理の途中でエラーが起きたら?

Aさんの口座から1000円引いたあと、Bさんへの加算処理でエラーが発生した——。処理をそのまま放置すると「Aさんの残高だけ減って、Bさんには届かない」という最悪の状態になります。

トランザクションを使えば「一連の処理をまとめて成功か失敗」にでき、途中で止まっても自動的に元の状態に戻る(ロールバック)ことを保証できます。

トランザクションの基本実装


<?php
$pdo->beginTransaction();
try {
    $pdo->exec("UPDATE accounts SET balance = balance - 1000 WHERE id = 1");
    $pdo->exec("UPDATE accounts SET balance = balance + 1000 WHERE id = 2");
    $pdo->commit();
} catch (Exception $e) {
    $pdo->rollBack();
    echo '処理を取り消しました: ' . $e->getMessage();
}

ACID特性とトランザクション

特性 意味 トランザクションでの保証
Atomicity(原子性) 全部成功か全部失敗 commit / rollBack
Consistency(一貫性) 制約を満たした状態を維持 DBの制約違反で自動ロールバック
Isolation(分離性) 並列処理でも干渉しない トランザクション分離レベルで制御
Durability(永続性) commitした内容は失われない DBのディスク保存

チェックポイント: 「複数のSQL文がセット」で意味をなす処理は必ずトランザクションで囲みましょう。注文処理・在庫更新・決済はその代表例です。

ネストしたトランザクションとセーブポイント


<?php
$pdo->beginTransaction();
try {
    $pdo->exec("INSERT INTO orders (user_id, total) VALUES (1, 5000)");
    
    // セーブポイントで部分ロールバックも可能
    $pdo->exec("SAVEPOINT before_items");
    
    $pdo->exec("INSERT INTO order_items (order_id, product_id) VALUES (1, 42)");
    $pdo->commit();
} catch (Exception $e) {
    $pdo->rollBack();
    throw $e;
}

チェックポイント: rollBack() を呼んでも例外を再スローしないと、上位の呼び出し元がエラーを検知できません。必ず throw $e を忘れずに。

まとめ & 次のステップ

  • トランザクションで「複数SQL処理の原子性」を保証する
  • エラー発生時は rollBack() で自動的に元の状態に戻る
  • catch ブロックでは必ずロールバック後に例外を再スローする

次回はN+1問題と対策を学びます。ループ内で発生する大量クエリを、JOINで根本的に解決する方法です。

Related Articles