🅿️

初心者必見!PHPとMySQLでのトランザクション処理でよくある失敗と解決策!!

に公開

はじめに

データベースのトランザクションは、複数の操作を1つの単位として扱い、処理の一貫性を保つために非常に重要な役割を担います。
特に、複数のデータベース操作を一度に行う場合、エラー発生時に適切にロールバックできることが求められます。
しかし、PHPとMySQLを使用したトランザクション処理においては、初心者がつまずきがちなポイントがいくつかあります。
本記事では、これらのつまずきポイントとその回避方法について詳しく解説します。

対象読者

  • PHPを使っている開発者
  • MySQLを使用している開発者
  • トランザクション管理の基本を学びたい方
  • 既存のコードでトランザクション周りに問題が発生している方

事前準備

本記事を進めるにあたって、以下の環境が整っていることを前提にしています。

  • PHP8.x(PHP8.0以上)
  • MySQL8.x
  • MySQLiまたはPDO拡張が有効化されていること

トランザクションの基本

データベースのトランザクションは、次の4つの特性(ACID特性)を持ちます。

  1. Atomicity(原子性):トランザクションは一つの単位として実行され、途中で失敗すればすべてがロールバックされます。
  2. Consistency(整合性):トランザクションが終了した時、データベースの状態が整合性のあるものになること。
  3. Isolation(独立性):トランザクションが他のトランザクションに影響を与えないこと。
  4. Durability(永続性):トランザクションがコミットされた場合、その変更は永続的に反映されます。

PHPでトランザクションを使う場合、通常は以下の手順を踏みます。

  1. 開始:トランザクションの開始
  2. 操作:複数のデータ操作を実行
  3. コミット:変更内容を確定
  4. ロールバック:エラーが発生した場合に変更内容を取り消し

基本的なトランザクションのコード例(PDO)

<?php
try {
    // PDO接続の初期化
    $pdo = new PDO('mysql:host=localhost;dbname=test', 'root', 'password');
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    // トランザクションの開始
    $pdo->beginTransaction();

    // 複数のSQL操作
    $pdo->exec("UPDATE users SET balance = balance - 100 WHERE id = 1");
    $pdo->exec("UPDATE users SET balance = balance + 100 WHERE id = 2");

    // コミット
    $pdo->commit();

} catch (Exception $e) {
    // エラー発生時のロールバック
    $pdo->rollBack();
    echo "Failed: " . $e->getMessage();
}
?>

初心者がつまずきやすいポイント

1. トランザクション開始前の自動コミット設定

MySQLではデフォルトで自動コミットが有効です。これにより、SQL文が実行されるたびに即座にコミットされてしまいます。トランザクションを使用する際には、最初に自動コミットを無効にする必要があります。

回避方法:

  • PDO::ATTR_AUTOCOMMITfalse に設定するか、mysqliの場合は mysqli_autocommit()false に設定することで、手動でコミット・ロールバックができるようになります。
$pdo->setAttribute(PDO::ATTR_AUTOCOMMIT, false); // 自動コミットを無効化

2. コミットとロールバックのタイミングを誤る

トランザクション内でエラーが発生しない限り、コミットを忘れることがあるため、予期しない挙動を引き起こします。また、エラー発生時にロールバックしないと、データが不整合な状態になります。

回避方法:

  • すべてのデータ操作が正常に完了した後に、コミットを行い、エラーが発生した場合は即座にロールバックを行うようにすることを心がけましょう。

3. ロールバックを忘れる

エラーが発生した場合にロールバックを行わずにそのまま続けてしまうと、データベースの整合性が損なわれます。これを避けるためには、例外を適切にキャッチし、ロールバック処理を忘れずに実行する必要があります。

回避方法:

  • 例外処理でcatchブロック内にrollBack()を適切に記述することを徹底しましょう。

4. 同時実行制御(トランザクション分離レベル)

MySQLのトランザクション分離レベルを適切に設定しないと、並列実行時にデータの競合が発生する可能性があります。これを避けるためには、トランザクション分離レベルを意識して設定することが重要です。

回避方法:

  • 分離レベルを設定することで、同時実行時のデータ競合を防ぎます。例えば、SERIALIZABLEREAD COMMITTED などを使用します。
$pdo->exec("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE");

5. トランザクション内での接続の再利用

トランザクション中に別のデータベース接続を使用すると、意図しない挙動が発生することがあります。特に、異なる接続に対してトランザクションを開始していると、コミットやロールバックが反映されません。

回避方法:

  • 1つの接続でトランザクションを開始し、その接続を使用してすべての操作を行うようにしましょう。

6. トランザクションを開いたまま長時間放置

トランザクションを開いたまま長時間放置すると、リソースの無駄遣いやデッドロックが発生する可能性があります。

回避方法:

  • トランザクション内での操作はできるだけ速やかに行い、コミットまたはロールバックを適切に行うようにします。

7. トランザクション内での自動エラー処理

PHPのエラー処理がうまく動作しない場合、トランザクション内で発生したエラーを適切にキャッチできないことがあります。

回避方法:

  • 明示的にtry-catchブロックを使用してエラーをキャッチし、ロールバックを行うようにします。

まとめ

PHP+MySQLでのトランザクション処理は非常に強力ですが、初心者がつまずきがちなポイントも多く存在します。
本記事で紹介したポイントを参考に、トランザクションの開始、コミット、ロールバックを正しく実装することで、データベースの整合性を保ちながら安定したシステムを構築することができます。

Discussion