CodeGym /コース /SQL SELF /トランザクション操作でよくあるミス

トランザクション操作でよくあるミス

SQL SELF
レベル 40 , レッスン 4
使用可能

このレクチャーでは、トランザクション操作でありがちなミスと、それをどうやって避けるかにフォーカスするよ。マジで、どんなSQLマスターでもたまにCOMMIT書き忘れることあるからね!ここで、トランザクションのミスをレアケースにするコツを伝授する。

残念ながら(ある意味ありがたいけど)、データベースって魔法の城じゃないから、全部が完璧に動くわけじゃない。トランザクション操作のミスは、特に初心者にはよくあること。詳しく見ていこう!

COMMITROLLBACKの書き忘れ

トランザクションを終わらせるのを忘れるのは、まさに「あるあるネタ」。レストランで料理頼んだのに、店員がレシート持ってくるの忘れた感じ。PostgreSQLの世界だと、DBが「トランザクション中」のまま止まって、リソースを掴んだまま他の操作をブロックしちゃう。

ミスの例:

BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
-- うっかり!COMMITかROLLBACKを追加し忘れた。

トランザクションが「止まった」ままだと、リソースのロックがテーブル全体に広がることもある。DB管理者が状況を把握していれば、強制終了できるけど、できればそうなる前に防ぎたいよね。

どうやって防ぐ?

  • 必ず明示的にトランザクションを終わらせよう:COMMITROLLBACK
  • トランザクションが止まってると教えてくれるクライアントツールを使うのもアリ。
  • トランザクションが終わってないままアプリを再起動すると、DBが自動でROLLBACKしてくれるけど、システムの状態的にはちょっと面倒なこともある。

間違ったアイソレーションレベルの選択

アイソレーションレベルの選択って地味だけど、異常系を防ぐのに超重要。例えば、重要な金融処理でREAD UNCOMMITTED使っちゃうと、「ダーティー」なデータを読んじゃって、後で取り消されることもある。

例:

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
BEGIN;
-- 他のトランザクションが変更中のデータを読む
SELECT balance FROM accounts WHERE account_id = 1;
-- 別のトランザクションがROLLBACKすると、読んだデータが無効になる。

どうやって防ぐ?

  • アプリにとってデータの重要度をちゃんと考えよう。
  • ほとんどのケースではREAD COMMITTEDを使って、「ダーティーリード」を避けよう。
  • 変更やファントムデータを絶対に避けたいときは、REPEATABLE READSERIALIZABLEみたいな厳しめのアイソレーションレベルを使おう。

トランザクションの競合とロック

たまに、2つ以上のトランザクションが同じデータを変更しようとすることがある。そうなると、PostgreSQLはどっちかが終わるまで片方をロックする。これがデッドロック(相互ロック)ってやつ。

ミスの例:

-- 1つ目のトランザクション
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;

-- 2つ目のトランザクション
BEGIN;
UPDATE accounts SET balance = balance + 100 WHERE account_id = 1;
-- 1つ目のトランザクションを待ってる...

両方のトランザクションが、お互いに必要なリソースを持ったままだと、行き詰まる。PostgreSQLはデッドロックを検出して、どちらか一方をエラーで終了させて、こんなメッセージを出す:

ERROR: deadlock detected

どうやって防ぐ?

  • トランザクション内の操作順序を固定しよう。
  • トランザクションの実行時間を短くして、ロックの確率を下げよう。
  • SERIALIZABLEレベルは本当に必要なときだけ使おう。

SAVEPOINTのミス

SAVEPOINTは部分的なロールバックに便利だけど、使い方を間違えるとややこしくなる。例えば、セーブポイント(RELEASE SAVEPOINT)を解放し忘れると、余計なロックやエラーの原因になる。

ミスの例:

BEGIN;
SAVEPOINT my_savepoint;
UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
ROLLBACK TO SAVEPOINT my_savepoint;
-- SAVEPOINTの解放を忘れた!

どうやって防ぐ?

  • もう使わないSAVEPOINTはちゃんと消そう。
  • セーブポイントを作りすぎるとクエリが複雑になるから、ほどほどに。

外部システムとのトランザクション非互換

例えば、PostgreSQLのトランザクションが外部システムとやり取りしようとする場合(通知送信、API更新など)。外部システムで何か問題が起きると、変更をロールバックするのが難しくなる。

例:

BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
-- 通知送信できない:email-serverが応答しない。
COMMIT; -- 変更は保存されたけど、通知は送れてない。

どうやって防ぐ?

  • できるだけ外部システムとのやり取りは分離しよう。
  • 中間テーブルやジョブキューを使って、外部システムとの連携を調整しよう。

大きなトランザクションによるミス

大量の操作を含む大きなトランザクションは、ロックやタイムアウト、デッドロックなどのミスが起きやすい。

例:

BEGIN;
-- 数千件の更新操作
UPDATE orders SET status = '完了' WHERE delivery_date < CURRENT_DATE;
COMMIT; -- 結構時間かかるかも。

どうやって防ぐ?

  • 大きなトランザクションは小さく分割しよう。
  • データ更新はバッチ処理を使おう。
  • 1つのトランザクションで変更するデータ量は最小限に。

エラーチェックのし忘れ

トランザクション内の全てのSQLクエリが必ず成功するとは限らない。例えば、どれかの操作でエラーが出ると、トランザクション全体が失敗状態になる。

例:

BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE account_id = -1; -- エラー:account_idが存在しない。
UPDATE accounts SET balance = balance + 100 WHERE account_id = 2;
COMMIT; -- エラーのせいで実行されない。

どうやって防ぐ?

  • 各操作の実行結果は必ずチェックしよう。
  • クエリやクライアントコードでエラー処理を入れよう。

ROLLBACKの動作の誤解

多くの開発者は、ROLLBACKで変更が全部元に戻ると思いがち。でもROLLBACKは、今のトランザクションの範囲でしか効かない。

誤解の例:

UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
ROLLBACK; -- エラー!トランザクションじゃないから効かない。

どうやって防ぐ?

  • BEGINは友達、これがないとROLLBACKは無力だって覚えとこう。
  • 重要な操作は必ずトランザクションで囲もう。
1
アンケート/クイズ
トランザクション分離レベル入門、レベル 40、レッスン 4
使用不可
トランザクション分離レベル入門
トランザクション分離レベル入門
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION