PHP
laravel

Laravelのトランザクションはネストできる

一部のRDBだけできるトランザクションのネストに対応してるってわけじゃなくて擬似的にネストさせてる。

内部的にトランザクションのカウント取っていて、
一番外側の \DB::commit() もしくは \DB::rollback() のみ実行される。

他のフレームワークでもこういう仕組み結構ありそう。

function tranA()
{
    \DB::transaction(function ()
        \DB::insert("QueryA");
        tranB(); // 別のトランザクション使った関数呼び出し
    });
}

function tranB()
{
    \DB::transaction(function ()
        \DB::insert("QueryB");
    });
}

tranA() を呼ぶと BEGIN --> QueryA --> QueryB --> COMMIT となるし、
tranB() を呼ぶと BEGIN --> QueryB --> COMMIT となる。

以下も同様

function tranA()
{
    \DB::beginTransaction();

    try {
        \DB::insert("QueryA");
        tranB(); // 別のトランザクション使った関数呼び出し
        \DB::commit();

    } catch (\Throwable $ex) {
        \DB::rollback();
        throw $ex;
    }
}

function tranB()
{
    \DB::beginTransaction();

    try {
        \DB::insert("QueryB");
        \DB::commit();

    } catch (\Throwable $ex) {
        \DB::rollback();
        throw $ex;
    }
}

と、ここまで書いておいてなんだけど個人的には使わないほうがいいと思う
理由は以下

  • コード追いかけてると「ファッ?!!!」って多くの人がなりそう
  • 書き方によってはQueryAだけ成功とかQueryBだけ成功ってケースができそう(思いつかなかった
  • トランザクションってRDBに対するひとまとまりの処理だからなんかヤダ

おまえ、もしかしてまだ、beginTransactionが死なないとでも思ってるんじゃないかね?

同じような書き方すると下記のような感じ。

function tranA()
{
    try {
        \DB::beginTransaction();
        \DB::insert("QueryA");
        tranB();
        \DB::commit();

    } catch (\Throwable $ex) {
        \DB::rollback();
        throw $ex;
    }
}

function tranB()
{
    try {
        \DB::beginTransaction(); // ここで失敗したとき
        \DB::insert("QueryB");
        \DB::commit();

    } catch (\Throwable $ex) {
        \DB::rollback();
        throw $ex;
    }
}

tranA() を呼び出して tranB()beginTransaction() で失敗すると、
その下の rollback() に対応してるのは tranA()beginTransaction() だぞ!!