CodeGym /コース /SQL SELF /データ整合性を守るためのトランザクションの使い方

データ整合性を守るためのトランザクションの使い方

SQL SELF
レベル 22 , レッスン 2
使用可能

例えば、ネットショップのアプリを作ってて、注文の支払いの時にやることがあるとするよ:

  1. お客さんのカードからお金を引き落とす。
  2. 倉庫の在庫数を減らす。
  3. 成功したトランザクションの記録を作る。

もしこの途中で何かトラブルが起きたらどうなる?例えば、お金は引き落としたけど、在庫がなくなって注文の記録が作れなかった場合。全部グチャグチャだよね:お金は「宙ぶらりん」、注文は未完了、サーバーには怒りのメール(もしかしたら訴訟も)大量に届く。

こういうのを防ぐためにトランザクションがあるんだ。複数の操作をまとめて「アトミック」な単位でDBに投げられる。テキストエディタの「元に戻す」ボタンみたいな感じ。何か失敗したら、最初に戻せる。

トランザクションはどうやってデータ整合性を守るの?

トランザクションはACIDっていう考え方に基づいてる:

  • アトミック性 (Atomicity) — トランザクション内の操作は全部やるか、全くやらないか。「全部かゼロか」だよ。
  • 一貫性 (Consistency) — トランザクションの前後でデータは一貫した状態になる。
  • 分離性 (Isolation) — 他のトランザクションに邪魔されない。
  • 永続性 (Durability) — トランザクションが終わったら、システム障害があっても結果は残る。

なんでまたこれを繰り返すのかって?だってこれが理想だから。でも…実際はなかなか全部は守れない。コースの後半でまたトランザクションやる時、ACIDのいくつかは妥協しなきゃいけないって分かるよ。

だから今は、トランザクションがシンプルでキレイな時代を楽しもう。じゃあ、例を見ていこう!

トランザクションの使い方の例

学生を追加してコースに登録するシナリオを見てみよう。

例えば、大学のDBを使ってるとする。コースに空きがあれば、外部の受講者を一時的に学生として登録してコースに追加する。流れはこんな感じ。

新しい学生をDBに追加してコースに登録する時は:

  1. studentsテーブルにレコードを追加。
  2. 学生とコースをつなぐenrollmentsテーブルにレコードを作る。

もし途中で何か失敗したら(例えばコースが満員)、操作をロールバックして、テーブル間でデータがズレないようにしなきゃ。やり方はこう:

-- トランザクション開始
BEGIN;

-- ステップ1: 学生を追加
INSERT INTO students (name, age, gender)
VALUES ('Otto Lin', 20, 'Male')
RETURNING id;

-- 例えば、id = 10 が返ってきたとする

-- ステップ2: コースに登録
INSERT INTO enrollments (student_id, course_id)
VALUES (10, 5);

-- 全部OKなら変更を確定
COMMIT;

エラーが起きたらどうなる?

例えば、コース登録でエラーが出た場合:コースが存在しないとか。トランザクションを忘れると、studentsテーブルには学生の記録が残るけど、enrollmentsには何もない。これじゃデータの整合性が崩れる。これを防ぐにはROLLBACKを使う。

-- トランザクション開始
BEGIN;

-- ステップ1: 学生を追加
INSERT INTO students (name, age, gender)
VALUES ('Otto Lin', 20, 'Male')
RETURNING id;

-- ステップ2: コースに登録しようとする
INSERT INTO enrollments (student_id, course_id)
VALUES (10, 999); -- エラー: id = 999 のコースは存在しない!

-- 全部ロールバック
ROLLBACK;

こうすると、どの操作も実行されず、DBはトランザクション前の状態に戻る。

SAVEPOINTでコントロールする

もっと複雑なシナリオを考えてみよう。いくつかの操作をやるけど、途中のあるポイントまでだけ戻したい、全部キャンセルじゃなくて。

じゃあ、ステップごとに学生を登録する例をやってみる

-- トランザクション開始
BEGIN;

-- 学生を追加
SAVEPOINT add_student; -- セーブポイント作成
INSERT INTO students (name, age, gender)
VALUES ('Anna Song', 22, 'Female');

-- 1つ目のコースに登録
SAVEPOINT enroll_course_1; -- さらにセーブポイント
INSERT INTO enrollments (student_id, course_id)
VALUES (11, 5);

-- 2つ目のコースに登録(ここでエラー)
INSERT INTO enrollments (student_id, course_id)
VALUES (11, 999); -- エラー!

-- 最後のセーブポイントまでロールバック
ROLLBACK TO enroll_course_1;

-- 続きをやる
INSERT INTO enrollments (student_id, course_id)
VALUES (11, 6);

-- 変更を確定
COMMIT;

こんな感じで、プロセスの一部でエラーがあっても、他の部分のデータはちゃんと残せる。

変更があったかどうかのチェック

SQLクエリで何か変更があったかどうか、実際に確認できる方法があるよ。

例えばDELETEを実行したけどWHEREに引っかかる行がなかったとか、UPDATEしたけどデータがすでに同じで何も変わらなかったとか。

そういう時は、システム変数FOUNDが使える。直前のSQLで行が変更されたかどうかを教えてくれる:

  • FOUND = TRUE — クエリで何か更新/削除があった;
  • FOUND = FALSE — 何も削除・変更されてない。

普通のSELECTには使えないけど、変更を追跡する時に便利。

実践例:支払い処理

トランザクションは特に金融系アプリで超重要。もう一回、口座から口座へお金を送るシステムを例にしよう。

-- トランザクション開始
BEGIN;

-- ステップ1: 1つ目の口座からお金を引く
UPDATE accounts
SET balance = balance - 100
WHERE id = 1 AND balance >= 100;

-- ステップ2: 操作が成功したかチェック(行が変更されたか)
IF NOT FOUND THEN
    ROLLBACK; -- 残高不足ならロールバック
    RAISE EXCEPTION '残高が足りないよ!'; -- エラー!例外を投げる
END IF;

-- ステップ3: 2つ目の口座にお金を足す
UPDATE accounts
SET balance = balance + 100
WHERE id = 2;

-- トランザクションを確定
COMMIT;

ここで、もしクライアントが口座残高以上のお金を送ろうとしたら、トランザクションはロールバックされて、DBが「宙ぶらりん」にならない。

特徴とよくあるミス

COMMIT忘れ: トランザクションの最後にCOMMITし忘れると、DBは「待ち状態」になって、変更が保存されない。

WHERE忘れ: 条件なしでデータを更新・削除すると大惨事になることも。例えばDELETE FROM studentsWHEREなしでやると、全学生が消える。

長いトランザクション: トランザクションを開きっぱなしにすると、データへのアクセスがブロックされてパフォーマンスが落ちる。必ずCOMMITROLLBACKで早めに終わらせよう。

トランザクションは、データ整合性を守る時の唯一の味方。特にユーザー登録、支払い処理、関連テーブルの更新みたいな複雑なシナリオで、データの不整合を防いでくれる。BEGINCOMMITROLLBACKSAVEPOINTを使いこなせば、もっと信頼できて安全なアプリが作れるよ。

2
タスク
SQL SELF, レベル 22, レッスン 2
ロック未解除
トランザクションの基本的な使い方
トランザクションの基本的な使い方
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION