DB::transaction() vs DB::beginTransaction(), DB::commit(), DB::rollback()
僕は圧倒的に DB::transaction() 派なのだが,レビューで指摘すると「なぜそこまで強く推すのか」と聞かれることがあるのでここに僕の考えをまとめておきたい.
以下に,DB::transaction() を使ったパターンと(以下トランザクションの自動管理,自動管理),DB::beginTransaction(), DB::commit(), DB::rollback() を使ったパターン(以下トランザクションの手動管理,手動管理)を簡単に書いてみる.
自動管理
code:php
use Illuminate\Support\Facades\DB;
DB::transaction(function () {
DB::update('update users set votes = 1');
DB::delete('delete from posts');
});
手動管理
code:php
use Illuminate\Support\Facades\DB;
DB::beginTransaction();
try {
DB::update('update users set votes = 1');
DB::delete('delete from posts');
DB::commit();
} catch (\Throwable $e) {
DB::rollback();
throw $e;
}
明らかにめんどくさいだろ!の一言で終わらせたい感.
DB::trasnaction() の中身がどうなっているか気になる人はコードを読むと良い
まず,手動トランザクションのデメリットとして,commit, rollback を書き忘れる可能性がある.
本人は気をつけているつもりでも,特にネストしていたり分岐が複雑になっている場合に抜け漏れが発生することは十分に考えられる.
commit のし忘れはもちろんデータが保存されないので致命的なバグになるし,rollback もしかり.変にデータが残ってしまう可能性がある.
開発者が意図的に commit や rollback のタイミングを細かく制御したい場合は別だけど.そもそもそんな要件はあんまりないはず.
あとは,例えば
code:php
try {
DB::beginTransaction();
// do something
DB::commit();
} catch (\Throwable) {
DB::rollback();
}
このコードには問題点がある.
これは,もし DB::beginTransaction() の呼び出しに失敗して例外が発生したとき,トランザクションを開始していないのに DB::rollback() が呼ばれる.
ネストしていない場合はまぁ致命的な問題はないかもしれないが,ネストしていたとき,一つ上のトランザクションに対して rollback してしまう可能性があるので,致命的なバグの元になる.(Laravel のトランザクションはネストでき,入れ子状態になりうるので.)
こういうバグをいちいちレビューで指摘するのは時間の無駄なので,ごそっと DB::transaction() でくくって,中のクロージャに処理を書いてください.