1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

DBのトランザクションの概要について理解する

Last updated at Posted at 2024-11-04

概要

トランザクションとは
複数の SQL 文によるデータ更新を1つの処理としてまとめてデータベースに反映させることです。
一部だけがデータベースに反映されると、データベース全体としてデータが不整合な状態になる場合に有効です。

背景 🖼️

業務でトランザクションを使ったのですが、口で説明できるほど概要がつかめていないので今回復習します。

トランザクションとは 💡

複数の SQL 文によるデータ更新を1つの処理としてまとめてデータベースに反映させることを言います。

一部だけがデータベースに反映されると、データベース全体としてデータが不整合な状態になる場合に有効です。

たいせい君がお父さんに送金する際の処理を考えます。(本当に送金してるかはそっとしておいてください)

Laravelで検証します。

こちらで動作確認可能です。

以下に公式docを参考にします。

テーブル

accounts(口座テーブル)
口座の情報を保持します。

Column Type Description
id INT ID(主キー)
name VARCHAR(50) ユーザ名
balance INT 残高

tradingHistories(取引履歴テーブル)
取引情報を保持します。

Column Type Description
id INT ID(主キー)
sender_id INT accountsテーブルのID(外部キー)
receiver_id INT accountsテーブルのID(外部キー)
trading_amount INT accountsテーブルのID(外部キー)

サンプルデータ挿入

リアリティあるデータを挿入します😢

accounts(口座テーブル)

id name balance
1 Taisei 100000
2 Father 100000000

トランザクションなしでクエリを発行する

お父さんに1000円送金します。

transaction-app/routes/web.php
<?php

use Illuminate\Support\Facades\Route;
use App\Models\Account;
use App\Models\TradingHistory;

Route::get('/', function () {

    $amount = 1000;
    $sender = Account::where('id', 1)->first();
    $receiver = Account::where('id', 2)->first();

    $sender->balance -= $amount;
    $receiver->balance += $amount;
    $sender->save();
    $receiver->save();

    TradingHistory::create([
        'sender_id' => $sender->id,
        'receiver_id' => $receiver->id,
        'trading_amount' => $amount,
    ]);

    return view('welcome');
});

クエリ発行後のテーブルは以下です。

accounts(口座テーブル)

id name balance updated_at created_at
1 Taisei 95000 2024-11-04 10:18:54 2024-11-04 10:14:33
2 Father 100005000 2024-11-04 10:18:54 2024-11-04 10:14:33

tradingHistories(取引履歴テーブル)

id sender_id receiver_id trading_amount updated_at created_at
1 1 2 1000 2024-11-04 10:18:54 2024-11-04 10:18:54

トランザクションなしでクエリを発行する際にエラーを起こし、不整合な状態にする

transaction-app/routes/web.php
$sender->save();
throw new \Exception();
$receiver->save();

お父さんに1000円送金しましたが、お父さんの口座に届いてないです。
これは不整合です。

accounts(口座テーブル)

id name balance updated_at created_at
1 Taisei 94000 2024-11-04 10:25:37 2024-11-04 10:14:33
2 Father 100005000 2024-11-04 10:18:54 2024-11-04 10:14:33

tradingHistories(取引履歴テーブル)

id sender_id receiver_id trading_amount updated_at created_at
1 1 2 1000 2024-11-04 10:18:54 2024-11-04 10:18:54

トランザクションを使用する ✨

transaction内のクロージャが正常に実行されると、トランザクションは自動的にコミットされてDBに反映されます。
クロージャー内で例外がスローされた場合、トランザクションは自動的にロールバックされ、例外は再度スローされます。

+---------------------+
|   トランザクション  |
|                     |
| +-----------------+ |
| |   SQLステートメント   | |
| | (データ操作、ここで複数のクエリが発行される)      | |
| +-----------------+ |
|         |           |
|         v           |
|    +----+----+      |
|    |         |      |
|    v         v      |
| +-------+   +------+ |
| | COMMIT |   | ROLLBACK | |
| +-------+   +------+ |
|     |            |     |
|     |            |     |
|     |            |     |
|     v            v     |
|  変更を保存      |   |  変更を取り消す    |
|   (データベース)  |   |  (データベース)    |
+---------------------+
transaction-app/routes/web.php
<?php

use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\DB; // DBファサードをインポート
use App\Models\Account;
use App\Models\TradingHistory;

Route::get('/', function () {
    $amount = 1000;

    // トランザクションを開始
    DB::transaction(function () use ($amount) {
        $sender = Account::where('id', 1)->first();
        $receiver = Account::where('id', 2)->first();

        // 残高の更新
        $sender->balance -= $amount;
        $receiver->balance += $amount;

        // 更新を保存
        $sender->save();
        $receiver->save();

        // 取引履歴の作成
        TradingHistory::create([
            'sender_id' => $sender->id,
            'receiver_id' => $receiver->id,
            'trading_amount' => $amount,
        ]);
    });

    return view('welcome');
});

次に送金してたいせい君の口座を保存してからエラーを起こします。

transaction-app/routes/web.php
<?php

use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\DB; // DBファサードをインポート
use App\Models\Account;
use App\Models\TradingHistory;

Route::get('/', function () {
    $amount = 1000;

    try {
        // トランザクションを開始
        DB::transaction(function () use ($amount) {
            $sender = Account::where('id', 1)->first();
            $receiver = Account::where('id', 2)->first();

            // 残高の更新
            $sender->balance -= $amount;
            $receiver->balance += $amount;

            // 更新を保存
            $sender->save();
            throw new \Exception();
            $receiver->save();

            // 取引履歴の作成
            TradingHistory::create([
                'sender_id' => $sender->id,
                'receiver_id' => $receiver->id,
                'trading_amount' => $amount,
            ]);
        });
    } catch (\Exception $e) {
        // トランザクションが失敗した場合の処理
    }

    return view('welcome');
});

テーブルを見ると、たいせい君の講座も更新されていないし、取引履歴のレコードも追加されていないです。
トランザクションによりDBの整合性が保たれています。

accounts(口座テーブル)

id name balance updated_at created_at
1 Taisei 94000 2024-11-04 10:25:37 2024-11-04 10:14:33
2 Father 100005000 2024-11-04 10:18:54 2024-11-04 10:14:33

tradingHistories(取引履歴テーブル)

id sender_id receiver_id trading_amount updated_at created_at
1 1 2 1000 2024-11-04 10:18:54 2024-11-04 10:18:54

参考 ✨

最後に

  • トランザクションに概要がつかめました
  • ロックだったり、まだ理解できていない所を勉強します
1
0
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
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?