【PL/SQL】DBMS_ALERTとDBMS_PIPEの使い方|セッション間通知・非同期メッセージ・実務注意点

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

DBMS_ALERTDBMS_PIPE は、Oracle Database内のセッション間で通知やメッセージをやり取りするためのPL/SQL標準パッケージです。どちらも外部メッセージブローカーを用意せずに使えますが、性質はかなり違います。

DBMS_ALERT は「イベントが起きたこと」を待機セッションへ通知する仕組みです。一方、DBMS_PIPE は名前付きパイプへメッセージを詰めて送受信する仕組みです。ただし、どちらも永続キューではありません。重要なイベント履歴、再実行保証、障害後の復旧が必要な処理では、テーブル、DBMS_SCHEDULER、Advanced Queuingなどと組み合わせて設計します。全体像は DBMS_PIPE・DBMS_ALERT完全ガイド も参考にしてください。

この記事で扱うこと

  • DBMS_ALERTDBMS_PIPE の違い
  • SIGNAL / WAITONE の基本
  • PACK_MESSAGE / SEND_MESSAGE / RECEIVE_MESSAGE の基本
  • コミット連動、タイムアウト、最新メッセージだけになる注意点
  • テーブルとALERTを組み合わせる実務パターン
  • PIPE名、private pipe、クリーンアップの注意点
スポンサーリンク

DBMS_ALERTとDBMS_PIPEの違い

まず、両者の使い分けを押さえます。DBMS_ALERT は通知のブロードキャストに近く、登録して待っているセッションへ「何かが起きた」と知らせます。DBMS_PIPE は名前付きパイプへメッセージを送り、受信側がそれを取り出す形です。

DBMS_ALERTイベント通知向きです。通知はコミット後に有効になり、登録セッションへ通知されます。
DBMS_PIPE簡易メッセージ送受信向きです。送信側で詰めたデータを受信側が取り出します。
永続性どちらも永続キューではありません。履歴や再実行保証が必要ならテーブルに記録します。
実務の使い方重要な仕事はテーブルに保存し、ALERTやPIPEは起動・通知のきっかけに使う設計が安全です。

DBMS_ALERTの基本形

DBMS_ALERT では、受信側が REGISTER でアラート名を登録し、WAITONEWAITANY で通知を待ちます。送信側は SIGNAL を呼び出します。

dbms-alert-waitone.sql
DECLARE
  v_message VARCHAR2(1800);
  v_status  PLS_INTEGER;
BEGIN
  DBMS_ALERT.REGISTER('ORDER_CHANGED');

  DBMS_ALERT.WAITONE(
    name    => 'ORDER_CHANGED',
    message => v_message,
    status  => v_status,
    timeout => 30
  );

  IF v_status = 0 THEN
    DBMS_OUTPUT.PUT_LINE('message=' || v_message);
  ELSE
    DBMS_OUTPUT.PUT_LINE('timeout or no alert');
  END IF;

  DBMS_ALERT.REMOVE('ORDER_CHANGED');
END;
/

WAITONEtimeout は、待ち続ける時間を秒で指定します。本番処理では無限待ちにせず、タイムアウトしたらログを出す、再試行する、終了する、といった動きを決めておきます。

SIGNALはコミット後に通知される

DBMS_ALERT.SIGNAL の重要な特徴は、通知がトランザクションのコミットと連動することです。送信側が SIGNAL を呼んでも、コミットされるまでは待機側へ通知されません。ロールバックすれば通知も無効になります。

dbms-alert-signal.sql
BEGIN
  UPDATE orders
  SET status = 'READY'
  WHERE order_id = 1001;

  DBMS_ALERT.SIGNAL(
    name    => 'ORDER_CHANGED',
    message => 'order_id=1001'
  );

  COMMIT; -- この時点で待機側に通知される
END;
/
注意: WAITONE / WAITANY で待機するセッションは、未コミットの作業を抱えたまま待たない設計にしてください。待機処理は専用セッションや専用ジョブに分けるほうが安全です。

DBMS_ALERTはキューではない

DBMS_ALERT はイベント通知であり、メッセージキューではありません。同じアラート名に対して複数回 SIGNAL された場合、待機側が過去の通知をすべて順番に受け取れるとは考えないほうが安全です。重要な処理対象は、必ずテーブルに保存し、ALERTは「テーブルを見に行くきっかけ」として使います。

alert-with-work-table.sql
-- 重要なイベントはテーブルに保存する
INSERT INTO app_event_queue (
  event_id,
  event_type,
  payload,
  status,
  created_at
) VALUES (
  app_event_seq.NEXTVAL,
  'ORDER_CHANGED',
  'order_id=1001',
  'READY',
  SYSTIMESTAMP
);

DBMS_ALERT.SIGNAL('APP_EVENT_READY', 'ORDER_CHANGED');

COMMIT;

待機側はALERTを受けたら、メッセージ本文だけで処理を決めるのではなく、テーブルから READY のイベントを取得して処理します。これにより、通知漏れやセッション停止があっても、未処理データを後から回収できます。

待機側ワーカーの実装例

以下は、ALERTを受けたらイベントテーブルを処理するワーカーの簡略例です。本番ではロック、リトライ回数、エラー記録、終了条件をもう少し丁寧に設計します。

alert-worker-loop.sql
DECLARE
  v_message VARCHAR2(1800);
  v_status  PLS_INTEGER;
BEGIN
  DBMS_ALERT.REGISTER('APP_EVENT_READY');

  LOOP
    DBMS_ALERT.WAITONE('APP_EVENT_READY', v_message, v_status, 60);

    IF v_status = 0 THEN
      FOR r IN (
        SELECT event_id, event_type, payload
        FROM app_event_queue
        WHERE status = 'READY'
        ORDER BY event_id
      ) LOOP
        UPDATE app_event_queue
        SET status = 'DONE',
            processed_at = SYSTIMESTAMP
        WHERE event_id = r.event_id
          AND status = 'READY';
      END LOOP;

      COMMIT;
    ELSE
      DBMS_OUTPUT.PUT_LINE('wait timeout');
    END IF;
  END LOOP;
END;
/

複数ワーカーで処理する場合は、二重処理を避けるための排他制御が必要です。Oracleのユーザー定義ロックは DBMS_LOCK完全ガイド、PL/SQLでの排他制御実装は DBMS_LOCKを使った排他制御 も参考になります。

DBMS_PIPEの基本形

DBMS_PIPE は、名前付きパイプへメッセージを送受信するパッケージです。送信側は RESET_BUFFER で送信バッファを初期化し、PACK_MESSAGE で値を詰め、SEND_MESSAGE でパイプへ送ります。受信側は RECEIVE_MESSAGE の後に UNPACK_MESSAGE で取り出します。

dbms-pipe-send-message.sql
DECLARE
  v_status PLS_INTEGER;
BEGIN
  DBMS_PIPE.RESET_BUFFER;
  DBMS_PIPE.PACK_MESSAGE('REFRESH_CACHE');
  DBMS_PIPE.PACK_MESSAGE('product');
  DBMS_PIPE.PACK_MESSAGE(1001);

  v_status := DBMS_PIPE.SEND_MESSAGE(
    pipename => 'APP_WORKER_PIPE',
    timeout  => 10
  );

  IF v_status <> 0 THEN
    RAISE_APPLICATION_ERROR(-20001, 'pipe send failed: ' || v_status);
  END IF;
END;
/
dbms-pipe-receive-message.sql
DECLARE
  v_status  PLS_INTEGER;
  v_command VARCHAR2(100);
  v_target  VARCHAR2(100);
  v_id      NUMBER;
BEGIN
  v_status := DBMS_PIPE.RECEIVE_MESSAGE(
    pipename => 'APP_WORKER_PIPE',
    timeout  => 30
  );

  IF v_status = 0 THEN
    DBMS_PIPE.UNPACK_MESSAGE(v_command);
    DBMS_PIPE.UNPACK_MESSAGE(v_target);
    DBMS_PIPE.UNPACK_MESSAGE(v_id);

    DBMS_OUTPUT.PUT_LINE(v_command || ':' || v_target || ':' || v_id);
  ELSE
    DBMS_OUTPUT.PUT_LINE('timeout or receive error: ' || v_status);
  END IF;
END;
/

private pipeと名前衝突に注意する

DBMS_PIPE のパイプ名は設計上のインターフェースになります。単純な名前にすると他処理と衝突したり、意図しない送受信が起きたりします。用途、アプリ名、環境名、所有者を含めた名前にし、必要に応じてprivate pipeを使います。

dbms-pipe-create-private.sql
DECLARE
  v_status PLS_INTEGER;
BEGIN
  v_status := DBMS_PIPE.CREATE_PIPE(
    pipename    => 'APP1.PROD.WORKER_CMD',
    maxpipesize => 8192,
    private     => TRUE
  );

  IF v_status <> 0 THEN
    RAISE_APPLICATION_ERROR(-20002, 'create pipe failed: ' || v_status);
  END IF;
END;
/

不要になった明示的なパイプは REMOVE_PIPE で削除します。一時的な通知用途でも、名前を決めずに乱立させると運用時に追いづらくなります。

dbms-pipe-remove-pipe.sql
DECLARE
  v_status PLS_INTEGER;
BEGIN
  v_status := DBMS_PIPE.REMOVE_PIPE('APP1.PROD.WORKER_CMD');
  DBMS_OUTPUT.PUT_LINE('remove status=' || v_status);
END;
/

DBMS_PIPEも永続キューではない

DBMS_PIPE のメッセージは、データベース内の一時的な通信バッファとして扱うべきです。ジョブの指示、重要な業務イベント、再実行が必要な処理をPIPEだけに載せると、受信側停止やインスタンス停止時に追跡できなくなります。確実に処理したい内容はテーブルに保存し、PIPEは起動合図や軽いコマンド通知として使います。

残してよいものキャッシュ再読込、軽い制御コマンド、手動運用の一時通知。
残してはいけないもの注文処理、課金、再実行が必要な業務イベント、監査ログ。
安全な設計本体はテーブルに保存し、PIPEは「処理開始の合図」に限定します。
監視送信失敗、受信タイムアウト、処理件数をログに残します。

DBMS_ALERTとDBMS_PIPEの使い分け

どちらを使うかは、送るものが「イベント通知」なのか「メッセージ」なのかで考えます。

DBMS_ALERT向きテーブルに新しい処理対象が入ったことを知らせる。複数待機セッションへ通知したい。
DBMS_PIPE向き1つの受信側へ簡単なコマンドや値を渡したい。メッセージ順序や受信処理を自前で管理したい。
DBMS_SCHEDULER向き定期実行、リトライ、実行履歴、異常検知まで管理したい。
AQや外部MQ向き永続キュー、複数コンシューマ、再配信、厳密な到達保証が必要。

ジョブ運用や実行履歴まで含めたい場合は DBMS_SCHEDULER完全ガイドジョブ実行履歴の集中管理と異常検知 が近いテーマです。

タイムアウトと終了条件を必ず入れる

待機処理では、WAITONERECEIVE_MESSAGE にタイムアウトを入れます。無限待ちにすると、終了指示、メンテナンス、障害時の停止が難しくなります。運用しやすいワーカーは、定期的に制御テーブルを見て終了指示や設定変更を確認します。

worker-stop-control.sql
DECLARE
  v_message VARCHAR2(1800);
  v_status  PLS_INTEGER;
  v_stop    NUMBER;
BEGIN
  DBMS_ALERT.REGISTER('APP_EVENT_READY');

  LOOP
    SELECT COUNT(*)
    INTO v_stop
    FROM app_control
    WHERE control_name = 'WORKER_STOP'
      AND control_value = 'Y';

    EXIT WHEN v_stop > 0;

    DBMS_ALERT.WAITONE('APP_EVENT_READY', v_message, v_status, 30);

    IF v_status = 0 THEN
      DBMS_OUTPUT.PUT_LINE('received: ' || v_message);
    END IF;
  END LOOP;

  DBMS_ALERT.REMOVE('APP_EVENT_READY');
END;
/

トリガーから通知する場合の注意

テーブル変更をきっかけに通知したい場合、トリガーから DBMS_ALERT.SIGNAL を呼ぶ設計があります。ただし、行トリガーで毎行SIGNALすると通知が過剰になりやすく、更新件数が多い処理では負荷やログ量が増えます。多くの場合、処理対象をテーブルへ記録し、文単位またはバッチ単位で通知するほうが扱いやすいです。

statement-trigger-alert.sql
CREATE OR REPLACE TRIGGER orders_notify_trg
AFTER INSERT OR UPDATE ON orders
BEGIN
  DBMS_ALERT.SIGNAL(
    name    => 'ORDER_CHANGED',
    message => 'orders changed'
  );
END;
/

トリガーが無効になるとDML自体に影響するため、監視とコンパイル確認も必要です。トリガー無効化の典型例は ORA-04098の原因と解決方法 にまとめています。

運用で確認したいログ

通知やパイプ通信は、動いている間は見えづらい処理になりがちです。いつ送信したか、受信したか、処理したか、タイムアウトしたかをアプリケーションログや管理テーブルに残します。

ipc-operation-log.sql
CREATE TABLE app_ipc_log (
  log_id      NUMBER GENERATED BY DEFAULT AS IDENTITY,
  channel     VARCHAR2(100),
  direction   VARCHAR2(10),
  status_code NUMBER,
  message     VARCHAR2(1800),
  log_time    TIMESTAMP DEFAULT SYSTIMESTAMP,
  CONSTRAINT pk_app_ipc_log PRIMARY KEY (log_id)
);

INSERT INTO app_ipc_log(channel, direction, status_code, message)
VALUES ('APP_EVENT_READY', 'RECEIVE', v_status, v_message);

PL/SQLのロギング設計は、単にエラーを出すだけではなく、再実行判断や障害調査に使える粒度で残すことが重要です。関連する監査・ロギング設計は ジョブ実行履歴の集中管理と異常検知 も参考になります。

よくある質問

Q. DBMS_ALERTは通知を全部キューに溜めますか?
A. いいえ。イベント通知として考えます。重要な処理対象はテーブルに保存し、ALERTは処理開始の合図にするのが安全です。
Q. SIGNALしたのに受信側が反応しません。
A. 送信側がコミットしているか、受信側がREGISTER済みか、アラート名が一致しているか、WAITのタイムアウトに到達していないかを確認します。
Q. DBMS_PIPEは業務キューとして使えますか?
A. 重要な業務キューには向きません。永続化、再配信、履歴管理が必要ならテーブル、DBMS_SCHEDULER、AQ、外部MQを検討します。
Q. 複数ワーカーで同時処理してもよいですか?
A. 可能ですが、イベントテーブルの行ロック、ステータス更新、DBMS_LOCKなどで二重処理を防ぐ設計が必要です。
Q. トリガーからSIGNALしてよいですか?
A. 使えますが、行トリガーで大量に通知する設計は避けます。文単位やバッチ単位の通知に寄せるほうが運用しやすいです。

まとめ

DBMS_ALERT はコミット連動のイベント通知、DBMS_PIPE は名前付きパイプによる簡易メッセージ通信です。どちらもセッション間通信には便利ですが、永続キューではありません。重要なイベントや再実行が必要な処理はテーブルに保存し、ALERTやPIPEは処理開始の合図や軽いコマンド通知に限定するのが安全です。

実務では、タイムアウト、終了条件、ログ、二重起動防止、クリーンアップまで含めて設計します。単純な通知ならDBMS_ALERT、値を渡す簡易通信ならDBMS_PIPE、履歴やリトライを含めるならDBMS_SCHEDULERや永続キューを検討しましょう。

参考: Oracle Database PL/SQL Packages and Types Reference – DBMS_ALERT / Oracle Database PL/SQL Packages and Types Reference – DBMS_PIPE