【PL/SQL】DBMS_ALERTとDBMS_PIPEでプロセス間通信を行う方法

【PL/SQL】DBMS_ALERTとDBMS_PIPEでプロセス間通信を行う方法 PL/SQL

Oracleデータベース内のセッション間で軽量に通知やメッセージをやり取りしたいとき、PL/SQLの標準パッケージであるDBMS_ALERTとDBMS_PIPEが役に立ちます。DBMS_ALERTは「イベント通知」に向いたコミット連動型の仕組みで、DBMS_PIPEは任意のデータをやり取りできる「メッセージパイプ」です。どちらもデータベース内で完結するため外部キューの導入なしにプロセス間通信(IPC)を実装できます。ここでは両者の特性と使い分け、基本API、すぐ試せるコード例、タイムアウトやトランザクションとの関係、運用時の勘所をまとめます。

DBMS_ALERTの基本と特性

DBMS_ALERTは名前付きのアラートを登録し、SIGNALで通知を送ってWAITONEやWAITANYで待ち受ける仕組みです。最大の特徴は通知の配送がコミットと連動する点で、SIGNALを発行しただけでは配信されず、通知側のトランザクションがCOMMITされた瞬間に待機側へ届きます。一方で通知内容は短い文字列メッセージに限られ、過去分の蓄積やリトライの概念はありません。用途としては「処理完了の合図」や「新着の存在を知らせるトリガ」のようなイベント駆動が適しています。

DBMS_ALERTの実装例(通知側と待受側)

-- セッションA:待受側(登録→待機→受信)
BEGIN
  DBMS_ALERT.REGISTER('JOB_DONE');
END;
/
DECLARE
  v_name    VARCHAR2(30);
  v_message VARCHAR2(4000);
  v_status  INTEGER;
BEGIN
  DBMS_ALERT.WAITONE(
    name      => 'JOB_DONE',
    message   => v_message,
    status    => v_status,
    timeout   => 60   -- 秒。0は待たない、-1は無期限
  );
  IF v_status = 0 THEN
    DBMS_OUTPUT.PUT_LINE('ALERT RECEIVED: ' || v_message);
  ELSE
    DBMS_OUTPUT.PUT_LINE('TIMEOUT OR ERROR status='||v_status);
  END IF;
  DBMS_ALERT.REMOVE('JOB_DONE'); -- 登録解除(不要なら省略)
END;
/
-- セッションB:通知側(SIGNAL→COMMITで配信)
BEGIN
  DBMS_ALERT.SIGNAL('JOB_DONE', 'step1 finished');
  COMMIT; -- ここで待受側へ配送される
END;
/

WAITANYを使えば複数名のアラートを横断的に待ち、どれが来たかをname引数で判定できます。長時間ジョブでは定期的にWAITを抜けるタイムアウト値を設定し、監視やキャンセル判定と併用する構成が実務的です。

DBMS_PIPEの基本と特性

DBMS_PIPEは名前付きパイプに対して送受信を行う仕組みで、PACK_MESSAGEで値を詰め、SEND_MESSAGEで送信し、受信側はRECEIVE_MESSAGEで取り出してUNPACK_*でデータを復元します。コミットに依存しないためサーバー内のセッション間で即時にデータを渡せます。文字列や数値など複数アイテムを順序通りに運べるため、軽量なジョブキューや制御メッセージの受け渡しに向きます。永続化や複数消費者への配布といった高度な要件は備えていないため、必要に応じて専用テーブルやAQ(Advanced Queuing)と使い分けます。

DBMS_PIPEの実装例(送信側と受信側)

-- セッションA:受信側(ブロッキング受信+UNPACK)
DECLARE
  v_rc     INTEGER;
  v_name   VARCHAR2(128) := 'PIPE_ORDER';
  v_cmd    VARCHAR2(30);
  v_id     NUMBER;
BEGIN
  -- 受信(タイムアウト30秒)。0=成功、1=タイムアウト
  v_rc := DBMS_PIPE.RECEIVE_MESSAGE(v_name, 30);
  IF v_rc = 0 THEN
    DBMS_PIPE.UNPACK_MESSAGE(v_cmd);
    DBMS_PIPE.UNPACK_MESSAGE(v_id);
    DBMS_OUTPUT.PUT_LINE('RECV cmd='||v_cmd||' id='||v_id);
  ELSE
    DBMS_OUTPUT.PUT_LINE('RECV TIMEOUT rc='||v_rc);
  END IF;
END;
/
-- セッションB:送信側(PACK→SEND)
DECLARE
  v_rc   INTEGER;
  v_name VARCHAR2(128) := 'PIPE_ORDER';
BEGIN
  DBMS_PIPE.PACK_MESSAGE('IMPORT'); -- 1個目のアイテム
  DBMS_PIPE.PACK_MESSAGE(101);      -- 2個目のアイテム
  v_rc := DBMS_PIPE.SEND_MESSAGE(v_name, 10); -- タイムアウト10秒
  IF v_rc = 0 THEN
    DBMS_OUTPUT.PUT_LINE('SEND OK');
  ELSE
    DBMS_OUTPUT.PUT_LINE('SEND FAILED rc='||v_rc);
  END IF;
END;
/

複数データ型を詰める際は送受でUNPACKの順番と型を必ず一致させます。パイプ名の衝突を避けたい場合はDBMS_PIPE.UNIQUE_SESSION_NAMEでユニーク名を作り、接続情報など別経路で共有します。

トランザクションとタイムアウトの扱い

DBMS_ALERTは通知がCOMMITで確定してから配信されるため、通知側のロールバック時には待機側へは届きません。ビジネス上の整合性を守りやすい反面、即時性が必要な場面では物足りないことがあります。DBMS_PIPEはCOMMITと無関係に即時配送されるため、即応性が求められる制御に適しますが、メッセージの永続化がないため受信前にセッションが落ちると失われます。両者ともタイムアウトを秒で指定でき、0を指定すると即時復帰、負の値を指定すると無期限待機になります。長時間待機は監視やキャンセルの仕組みとセットで設計すると安全です。

エラーハンドリングとクリーンアップの注意点

DBMS_ALERTのWAIT系はステータスコードで復帰するため、タイムアウトと例外を混同しない分岐が必要です。REGISTERしたアラートはREMOVEまたはREMOVEALLで解除できます。DBMS_PIPEのSEND/RECEIVEは戻り値で成否を判断し、0以外の値をログに残すと原因追跡が容易です。PACK/UNPACKの型順序が一致しないと例外が発生するため、共通パッケージでメッセージフォーマットを一元化し、変更時は両端を同時に更新する方針が実務的です。

使い分けの指針と実務パターン

コミット整合性を保った「完了通知」や「新規データの到着シグナル」を出したいときはDBMS_ALERTが適しています。通知を受けた側は詳細データをテーブルから読み取り、処理を続行します。即時の制御メッセージや小さなペイロードの受け渡し、軽量なワーカー起動合図のような用途にはDBMS_PIPEが向いています。どちらも恒久的なキューではないため、失敗時の再実行や冪等設計はアプリケーション側で担保し、重要イベントの履歴は専用テーブルへ永続化する設計が安定します。

まとめ

DBMS_ALERTはコミット同期のイベント通知、DBMS_PIPEは即時配送の軽量メッセージングという性格を持ちます。コミットに乗せて確実に知らせたいのか、即応性を優先して小さなコマンドを渡したいのかで選択し、タイムアウトや例外、登録解除やフォーマット統一といった運用の型を決めておけば、外部ミドルウェアに頼らずにデータベース内だけでシンプルなプロセス間通信を実現できます。