【Laravel】 データベースの操作でトランザクションする

RDBMSの利点は、ACIDです。トランザクションを行うことで中途半端なデータを生じさせることを防ぎ、厳密なデータの管理が可能になります。

トランザクションとは

トランザクションとは、ひとまとまりの処理をまとめたものです。RDBMSでトランザクションを活用すると、このひとまとまりの途中で失敗した場合に全てなかったことにできたり、処理中に他が書き込んだりといったことを防ぐ事ができます。

Laravelでは、DB::transactionを用いて囲んだ部分をトランザクションさせる方法と、DB::beginTransaction()を使用して手動でトランザクションの開始と終了、コミット等をする方法の2種類があります。

トランザクションする

実際にトランザクションしてみましょう。

いずれのトランザクション方法においても、トランザクション範囲にはモデルやDBの操作以外のもの (データの整形など) を入れるべきではありません。DB以外の原因で起こりうるエラーでロールバックされたり、例外の記録がしづらくなったりするためです。

DB::transaction

DB::transactionに入れた関数の中でトランザクションします。コミットやロールバックが自動で行われます。

基本的にはこちらの方法で問題ありません。ロールバック漏れやコミット漏れが起こることが考えづらいためです。

use Illuminate\Support\Facades\DB;

class HelloController extends Controller
{
    public function sampleStore(Request $request)
    {
        DB::transaction(function () use (&$request) {
            $note = Note::find(1);
            $note->content = $request->input('content1');
            $note->save();

            $note2 = Note::find(2);
            $note2->content = $request->input('content2');
            $note2->save();
        });

        return /* ... */;
    }
}

無名関数を入れることが多いと思いますので、useし忘れないようにしましょう。

上は若干強引な例ですが、例えば$note->save()に成功し、$note2->save()には失敗したとします。その場合はロールバックされるため、$note$note2でデータベースに加えた変更が両方なかったことになります。

また、transactionメソッドの第2引数に最大試行回数を指定することもできます。

DB::transaction(function () use (&$request) {
    // ...
}, 3);
// ↑ 最大試行回数

例外排出の場合

transaction内で例外が排出されるとアプリケーションが止まってしまうので、必ずキャッチするようにしましょう。

try {
    DB::transaction(function () use (&$request) {
        $note2 = Note::find(2);
        // 存在しないカラム名を使うと例外排出される
        $note2->aaaa = $request->input('content2');
        $note2->save();
    }, 3);
} catch (\Exception $e) {
    Log::error($e);
}

排出される例外はPDOExceptionIlluminate\Database\QueryException等です。本番運用では前者が多いと思います。

DB::beginTransaction()

DB::beginTransaction()でトランザクションを開始し、手動でコミットやロールバックを指示する方法もあります。

try {
    // トランザクション開始
    DB::beginTransaction();

    // 適当に色々書き換え
    $note = Note::find(1);
    $note->content = $request->input('content1');
    $note->save();

    // トランザクション開始してからここまでのDB操作を適用
    DB::commit();
} catch (Exception $e) {
    // トランザクション開始してからここまでのDB操作を無かったことにする
    DB::rollBack();
    Log::error($e);
}

この方法ではbeginしてからDB::commitDB::rollbackされるまでの操作がトランザクション対象となります。そのため、外のメソッドを実行してそこでモデルの書き換え処理が走る等の場合に向いています。

ロールバック漏れやコミット漏れには十分に注意する必要があります。コミット漏れはテスト段階で気が付きやすいですが、ロールバック漏れは忘れがちなので十分にチェックするべきです。

まとめ

Laravelでなくとも、RDBMSを使用した場合にトランザクションを行うことは必要不可欠です。アカウントの登録や削除処理、課金処理といった、複数のモデルを書き換えるような処理を行う場合は、データの不整合が生まれないよう、必ずトランザクションするべきです。

2種類のトランザクション方法は適宜使い分けていきましょう。

役に立ったらシェアしよう!