LoginSignup
11
11

More than 3 years have passed since last update.

[Laravel 5.8] トランザクションのネストとカウント

Last updated at Posted at 2019-08-16

コミット

ネストされたDB::commit()は無視され、最も浅いDB::commit()でコミットされます。

以下の例では、DBにユーザー2人が作成されるのは、t1()のDB::commit()実行時です。
t2()内のDB::commit()では、DBにユーザーは誰も作成されていません。

function t1()
{
    DB::beginTransaction();
    User::create(['id' => 1]);
    t2();
    sleep(10);
    DB::commit(); // ここでコミットされる
}

function t2()
{
    DB::beginTransaction();
    User::create(['id' => 2]);
    DB::commit(); // ここではコミットされない
}

t1();

sleep(10)実行中に、DBでselect * from users;することで、コミットされていないことを確認できます。

Laravelはトランザクションのカウントを取っている

トランザクションのカウント($this->transactions)を取り、そのカウントが1の時のみコミットを実行しているようです。
カウントはトランザクション開始するごとに増え、コミットするごとに減っています。

vendor/laravel/framework/src/Illuminate/Database/Concerns/ManagesTransactions.php
public function beginTransaction()
{
    $this->createTransaction();

    $this->transactions++;

    $this->fireConnectionEvent('beganTransaction');
}

public function commit()
{
    if ($this->transactions == 1) {
        $this->getPdo()->commit();
    }

    $this->transactions = max(0, $this->transactions - 1);

    $this->fireConnectionEvent('committed');
}

ちなみに2回目以降のトランザクション開始はセーブポイント作成になるようです。
参考:https://github.com/laravel/framework/blob/5.8/src/Illuminate/Database/Concerns/ManagesTransactions.php

DB::commit()し忘れると

トランザクションのカウントはDB::beginTransaction()するごとに増えていきますので、1つ忘れると全てのトランザクションがコミットされません。
シンプルな例ですが、以下はDBにユーザーは誰も作成されません。

DB::beginTransaction();
User::create(['id' => 1]);
// DB::commit(); // コミット忘れた

DB::beginTransaction();
User::create(['id' => 2]);
DB::commit();

ロールバック

DB::rollBack()した時もトランザクションのカウントが1つ減ります。また2回目以降のDB::beginTransaction()はセーブポイント作成になります。
よって以下の例は、id1のユーザーのみ作成されます。

DB::beginTransaction();
User::create(['id' => 1]);

DB::beginTransaction();
User::create(['id' => 2]);

DB::rollBack();
DB::commit();

以下の例では、id1のユーザーのみDBに作成されます。
t2()で例外がスローされていますが握りつぶしているため、t2()はロールバック、t1()はコミットという結果になっています。

function t1()
{
    try {
        DB::beginTransaction();
        User::create(['id' => 1]);
        t2();
        DB::commit();
    } catch (Exception $e) {
        DB::rollBack();
    }
}

function t2()
{
    try {
        DB::beginTransaction();
        User::create(['id' => 2]);
        throw new Exception; // 例外発生
        DB::commit();
    } catch (Exception $e) {
        DB::rollBack();
        // ここでthrow $eせず、例外を握りつぶしている
    }
}

t1();

例外を握り潰さないようにすると、全てロールバックされます。

// id1もid2も作成されない
\DB::transaction(function () {
    User::create(['id' => 1]);
    \DB::transaction(function () {
        User::create(['id' => 2]);
        throw new Exception;
    });
});
11
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
11