CodeGym /コース /SQL SELF /トランザクション操作時の制限と潜在的な問題: LOCK, DEADLOCK

トランザクション操作時の制限と潜在的な問題: LOCK, DEADLOCK

SQL SELF
レベル 54 , レッスン 1
使用可能

トランザクション操作時の制限と潜在的な問題: LOCK, DEADLOCK

PostgreSQLのトランザクションは、ACIDの重要な特徴の一つである分離性(isolation)を保証してくれる。そのためにシステムはロックを使うんだ。ロックっていうのは、複数のトランザクションが同じレコードやテーブルを「取り合い」しないようにする仕組み。スーパーのレジの列を想像してみて。一度に一人しか会計できないよね。ロックも同じで、テーブルのデータへのアクセスを管理してる感じ。

主なロックの種類

PostgreSQLにはいくつかロックのタイプがある。もしかしたらEXPLAIN ANALYZEで見たことあるかも:

  1. ROW EXCLUSIVE(行排他ロック) — 行データを変更するときに発生する。一番よく使われるロックタイプで、たとえばINSERTUPDATEDELETEの時に使われる。
  2. SHARE(共有ロック) — クエリ実行中にデータが変更されないことを保証したいときに使う。たとえばSELECT ... FOR SHAREの時とか。
  3. EXCLUSIVE(排他ロック) — 読み取り以外の全ての操作をブロックする。

ロックは、データを守る番犬みたいなもんだって思ってくれてOK。

ロックの問題点:何がうまくいかなくなる?

ロックが何か分かったところで、どんな問題が起きるか見てみよう。一番大きいのはデッドロック問題。デッドロック(deadlock)ってのは、2つ以上のトランザクションがお互いを待ち続けて、永遠に進まなくなる状況のこと。典型的にはこんな感じ:

  1. トランザクション1が行Aをロックして、行Bにアクセスしようとする。
  2. トランザクション2が行Bをロックして、行Aにアクセスしようとする。
  3. 両方の行がロックされてるから、どっちのトランザクションも終われない。

デッドロックの例

デッドロックにぶち当たったプログラマーの苦しみを感じてみて。こんなシナリオ:

-- トランザクション1
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;

-- 同時に
-- トランザクション2
BEGIN;
UPDATE accounts SET balance = balance + 200 WHERE id = 2;

-- トランザクション1がid = 2をロックしようとする
UPDATE accounts SET balance = balance - 100 WHERE id = 2;

-- トランザクション2がid = 1をロックしようとする
UPDATE accounts SET balance = balance + 200 WHERE id = 1;

ほらね!それぞれのトランザクションが相手にロックされてる。PostgreSQLはそのうちこれを検出して、デッドロックエラーを投げてくる。

デッドロックをどう回避する?

  1. データアクセスの順番を統一しよう。 いつも同じ順番でデータにアクセスするようにしよう。もしトランザクション1が最初に行A、次に行Bをロックするなら、トランザクション2も同じ順番(まず行A、次に行B)でアクセスするべき。
-- 正しいアクセス順
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 200 WHERE id = 2;
COMMIT;
  1. トランザクションの長さを最小限にしよう。 長いトランザクションはデッドロックのリスクを高める。必要な変更を全部まとめてBEGINして、すぐにCOMMITしよう。

  2. トランザクション分離レベルを使い分けよう。 もし厳密なデータ分離が必要ないなら、READ COMMITTEDレベルを使うのもアリ。これで一部のロックを回避できる。

タイムアウトとロック診断

PostgreSQLにはロックの診断や回避のためのツールがある。たとえば、ロックの最大待ち時間を設定できる:

SET lock_timeout = '5s';  -- ロックタイムアウトを5秒に設定

ロックが指定時間以上続いたら、そのトランザクションは中断される。これで完全な行き詰まりを防げる。

PostgreSQLでロックを監視する

ロック監視に便利なコマンドの一つがpg_locks。これでシステム内の全てのアクティブなロックが見える:

SELECT * FROM pg_locks;

どのトランザクションがロックを保持していて、どれが解除待ちか分かる。デッドロックのデバッグにも超便利。

LOCKの特徴と手動ロック

手動でロックを管理したい場合は、LOCKコマンドを使おう:

LOCK TABLE orders IN ACCESS EXCLUSIVE MODE;

注意:ACCESS EXCLUSIVEは最強のロックで、SELECTすらできなくなる。他の操作を全部ブロックするから、テーブル構造を変える時とか特別な場合だけ使おう。

特定の行だけロックしたい時はSELECT ... FOR UPDATEを使う:

SELECT * FROM accounts WHERE id = 1 FOR UPDATE;

これで他のトランザクションがその行を変更できなくなる。自分のトランザクションが終わるまで安心。

ロックで覚えておくべきこと

ロックは悪者じゃなくてツール。データの整合性を守るためのものだけど、使い方には注意が必要。ポイントは:

  1. トランザクションが本当に必要か分からないなら始めない。
  2. できるだけ早くトランザクションを終わらせる。
  3. データアクセスの順番をバラバラにしない。
  4. 自分の用途に合った分離レベルを選ぶ。

これでロックやデッドロックとも上手く付き合えるはず!pg_locksとACIDの知恵が君と共にあらんことを。

コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION