この記事の最終更新日: 2023年6月2日
Laravelは、一連の操作がすべて成功するか、一つでも失敗したら全部を取り消すという「全てか無し」の原則をトランザクションとして実装しています。しかし、トランザクションの中に別のトランザクション(ネストしたトランザクション)を貼ると、挙動が少し複雑になります。この記事では、その挙動と注意点を解説します。
トランザクションの基本
まずはトランザクションの基本をおさらいします。トランザクションとは、複数のデータベース操作を一つの作業単位として扱う技術のことを指します。トランザクションが開始されると、その中で行われる全ての操作は一つのまとまりとなり、全ての操作が成功するか、一つでも失敗したら全てが取り消されます。
Laravelでは、DB::transaction
メソッドを使用してトランザクションを開始します。このメソッドはクロージャを受け取り、そのクロージャの中で行われる全てのデータベース操作をトランザクションとして扱います。
ネストしたトランザクションの挙動
では、ネストしたトランザクションはどのように動作するのでしょうか。Laravelでは、既に開始されているトランザクションの中で新たにトランザクションを開始すると、その新たなトランザクションは「セーブポイント」として扱われます。
このセーブポイントは、外側のトランザクションが続行されている間は通常のトランザクションと同じように動作します。しかし、外側のトランザクションがロールバックされると、セーブポイントもロールバックされます。
注意点(その1)
ここで注意しなければならないのは、内側のトランザクション(セーブポイント)がロールバックされても、外側のトランザクションはそのまま続行されるという点です。つまり、内側のトランザクションでエラーが発生し、それがロールバックされても、そのエラーは外側のトランザクションには伝播しません。
注意点(その2)
Laravel 6以降、現在のトランザクションのレベルを取得する方法が提供されています。DB::connection(DB::getDefaultConnection())->transactionLevel()
を使用すると、現在のトランザクションのレベル(ネストの深さ)を取得できます。この機能を利用することで、トランザクションのネストをより安全に扱うことが可能になります1。
具体的には、トランザクションが開始されるたびにレベルが増加するため、トランザクションを開始する前にレベルをチェックし、レベルが0(すなわちトランザクションがまだ開始されていない)場合のみ新たにトランザクションを開始します。そして、トランザクションのコミットは、トランザクションを開始したメソッド内でのみ行われます。これにより、複数のサービスがそれぞれ独自のトランザクション管理を持つ場合でも、安全にトランザクションを管理することが可能になります1。
また、内側のトランザクションがエラーをスローすると、そのエラーは外側のトランザクションには伝播しないため、内側のトランザクションをtry-catchブロックでラップすることで、内側のトランザクションがロールバックされても外側のトランザクションを継続することができます。しかし、外側のトランザクションがエラーをスローすると、そのネストされた全てのトランザクションもロールバックされます2。
まとめ
Laravelのネストしたトランザクションは、単一のトランザクション内で複数の作業を行う際に非常に有用な機能です。しかし、その挙動と制限事項を理解しておくことは非常に重要です。ネストされたトランザクションがエラーをスローすると、そのネストされたトランザクションだけがロールバックされ、親のトランザクションは続行されます。しかし、親のトランザクションがエラーをスローすると、そのすべてのネストされたトランザクションもロールバックされます。また、ネストされたトランザクションを使用するときはパフォーマンスにも注意が必要です。これらの注意点を把握しておけば、Laravelのネストされたトランザクションを効果的に使用することができます。
大阪のエンジニアが書いているブログ。
コメント