背景 🖼️
業務でトランザクションを使ったのですが、口で説明できるほど概要がつかめていないので今回復習します。
トランザクションとは 💡
複数の 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円送金します。
<?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 |
トランザクションなしでクエリを発行する際にエラーを起こし、不整合な状態にする
$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 |
| 変更を保存 | | 変更を取り消す |
| (データベース) | | (データベース) |
+---------------------+
<?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');
});
次に送金してたいせい君の口座を保存してからエラーを起こします。
<?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 |
参考 ✨
最後に
- トランザクションに概要がつかめました
- ロックだったり、まだ理解できていない所を勉強します