Oracle のセッション間で情報をやりとりしたい場面があります。バッチジョブが処理完了を別のセッションに通知する、DML トリガーが変更イベントをアプリケーションに送信するといった要件です。
Oracle には 2 種類のセッション間通信パッケージが用意されています。DBMS_PIPEは同期的なメッセージパイプで、送信側と受信側が存在し双方向通信ができます。DBMS_ALERTは非同期イベント通知で、イベントが発生したことをサブスクライブしているセッションに一斉通知します。
この記事でわかること
- DBMS_PIPE と DBMS_ALERT の仕組みと使い分け
- DBMS_PIPE でメッセージを送受信する方法(PACK_MESSAGE / SEND_MESSAGE / RECEIVE_MESSAGE)
- 公開パイプとプライベートパイプの違い
- DBMS_ALERT でイベントを登録・通知・待機する方法(REGISTER / SIGNAL / WAITONE)
- DBMS_PIPE / DBMS_ALERT に必要な権限
DBMS_PIPE と DBMS_ALERT の比較
| 項目 | DBMS_PIPE | DBMS_ALERT |
|---|---|---|
| 通信モデル | 1対1(または複数セッションが取り合い) | 1対多(1 つのシグナルを複数受信者が受け取る) |
| メッセージの消費 | RECEIVE_MESSAGE で受け取ると消える | WAITONE で受け取っても他のサブスクライバはまだ受け取れる |
| トランザクション連動 | トランザクションと無関係(ROLLBACK しても送信済み) | COMMIT 時に通知される(ROLLBACK では通知されない) |
| 格納場所 | SGA(共有メモリ) | データベース(ALERT$ テーブル) |
| タイムアウト | RECEIVE_MESSAGE の timeout で指定 | WAITONE の timeout で指定 |
| 主な用途 | バッチ間のメッセージパッシング・ステータス確認 | DML 後のアプリ通知・イベント駆動処理 |
DBMS_PIPE の使い方
メッセージを送信する(送信セッション)
DBMS_PIPE でメッセージを送信する
-- プライベートパイプを作成する(作成したセッションのみアクセス可能)
DECLARE
v_status NUMBER;
BEGIN
-- CREATE_PIPE でパイプを明示的に作成する
-- public => FALSE(プライベートパイプ)/ TRUE(公開パイプ)
v_status := DBMS_PIPE.CREATE_PIPE(
pipename => 'BATCH_STATUS_PIPE',
maxpipesize => 8192, -- パイプの最大サイズ(バイト)
private => TRUE -- TRUE: プライベートパイプ(所有者のみアクセス可)
);
IF v_status != 0 THEN
DBMS_OUTPUT.PUT_LINE('パイプ作成失敗: ' || v_status);
RETURN;
END IF;
END;
/
-- パイプにメッセージを送信する
DECLARE
v_status NUMBER;
BEGIN
-- PACK_MESSAGE でメッセージバッファに値を積む(複数の値を積める)
DBMS_PIPE.PACK_MESSAGE('BATCH_COMPLETE'); -- VARCHAR2
DBMS_PIPE.PACK_MESSAGE(SQL%ROWCOUNT); -- NUMBER
DBMS_PIPE.PACK_MESSAGE(SYSDATE); -- DATE
-- SEND_MESSAGE でパイプに送信する
v_status := DBMS_PIPE.SEND_MESSAGE(
pipename => 'BATCH_STATUS_PIPE',
timeout => 10, -- タイムアウト秒(パイプが満杯のとき待つ時間)
maxpipesize => 8192
);
IF v_status = 0 THEN
DBMS_OUTPUT.PUT_LINE('送信成功');
ELSIF v_status = 1 THEN
DBMS_OUTPUT.PUT_LINE('タイムアウト(パイプが満杯)');
ELSE
DBMS_OUTPUT.PUT_LINE('送信エラー: ' || v_status);
END IF;
END;
/
メッセージを受信する(受信セッション)
DBMS_PIPE でメッセージを受信する
-- パイプからメッセージを受信する
DECLARE
v_status NUMBER;
v_msg_type NUMBER;
v_msg_str VARCHAR2(4000);
v_msg_num NUMBER;
v_msg_date DATE;
BEGIN
-- RECEIVE_MESSAGE でパイプからメッセージを受信する
v_status := DBMS_PIPE.RECEIVE_MESSAGE(
pipename => 'BATCH_STATUS_PIPE',
timeout => 30 -- 最大 30 秒待機(0 = ノーウェイト、-1 = 無限待機は非推奨)
);
IF v_status = 0 THEN
-- 受信成功: UNPACK_MESSAGE でバッファから値を取り出す
-- 積んだ順番と同じ順で取り出す
DBMS_PIPE.UNPACK_MESSAGE(v_msg_str); -- 文字列
DBMS_PIPE.UNPACK_MESSAGE(v_msg_num); -- 数値
DBMS_PIPE.UNPACK_MESSAGE(v_msg_date); -- 日付
DBMS_OUTPUT.PUT_LINE('メッセージ: ' || v_msg_str);
DBMS_OUTPUT.PUT_LINE('件数: ' || v_msg_num);
DBMS_OUTPUT.PUT_LINE('日時: ' || TO_CHAR(v_msg_date, 'YYYY-MM-DD HH24:MI:SS'));
ELSIF v_status = 1 THEN
DBMS_OUTPUT.PUT_LINE('タイムアウト: メッセージなし');
ELSIF v_status = 2 THEN
DBMS_OUTPUT.PUT_LINE('メッセージが大きすぎる(バッファ不足)');
ELSE
DBMS_OUTPUT.PUT_LINE('受信エラー: ' || v_status);
END IF;
END;
/
-- パイプのクリーンアップ(使用後はパイプを削除する)
DECLARE
v_status NUMBER;
BEGIN
v_status := DBMS_PIPE.REMOVE_PIPE('BATCH_STATUS_PIPE');
END;
/
DBMS_ALERT の使い方
アラートを受信する(サブスクライバ)
DBMS_ALERT にアラートを登録して待機する(受信側)
-- アラートを受信するセッション
DECLARE
v_message VARCHAR2(1800);
v_status NUMBER;
BEGIN
-- REGISTER でアラート名を登録する(受け取りたいアラートを登録)
DBMS_ALERT.REGISTER('ORDERS_INSERT_ALERT');
DBMS_ALERT.REGISTER('ORDERS_UPDATE_ALERT');
DBMS_OUTPUT.PUT_LINE('アラート待機中...');
-- WAITONE で特定のアラートが来るまで待機する
DBMS_ALERT.WAITONE(
name => 'ORDERS_INSERT_ALERT',
message => v_message, -- アラートとともに送られたメッセージを格納
status => v_status, -- 0=受信成功, 1=タイムアウト
timeout => 60 -- 最大 60 秒待機
);
IF v_status = 0 THEN
DBMS_OUTPUT.PUT_LINE('アラート受信: ' || v_message);
ELSE
DBMS_OUTPUT.PUT_LINE('タイムアウト');
END IF;
-- セッション終了時はアラート登録を解除する
DBMS_ALERT.REMOVE('ORDERS_INSERT_ALERT');
DBMS_ALERT.REMOVEALL; -- すべてのアラート登録を一括解除
END;
/
アラートを送信する(シグナル側)
トリガーからアラートを送信する(送信側)
-- orders テーブルへの INSERT をアラートで通知するトリガー
-- DBMS_ALERT.SIGNAL は COMMIT 時に通知される(ROLLBACK では通知されない)
CREATE OR REPLACE TRIGGER trg_orders_alert
AFTER INSERT ON orders
FOR EACH ROW
BEGIN
-- SIGNAL でアラートを送信する(COMMIT 後に受信側に届く)
DBMS_ALERT.SIGNAL(
name => 'ORDERS_INSERT_ALERT',
message => 'order_id=' || :NEW.order_id || ', customer_id=' || :NEW.customer_id
);
-- 注意: SIGNAL はトランザクションの COMMIT を待って通知される
-- このトリガーの COMMIT 前は受信側に届かない
END trg_orders_alert;
/
-- PL/SQL プロシージャからもアラートを送信できる
BEGIN
DBMS_ALERT.SIGNAL('BATCH_DONE', '月次バッチが完了しました');
COMMIT; -- COMMIT をしてアラートを発火させる
END;
/
-- WAITANY で複数のアラートをまとめて待機する
DECLARE
v_name VARCHAR2(30);
v_message VARCHAR2(1800);
v_status NUMBER;
BEGIN
DBMS_ALERT.REGISTER('ORDERS_INSERT_ALERT');
DBMS_ALERT.REGISTER('BATCH_DONE');
-- いずれかのアラートが来るまで待機する
DBMS_ALERT.WAITANY(
name => v_name,
message => v_message,
status => v_status,
timeout => 120
);
IF v_status = 0 THEN
DBMS_OUTPUT.PUT_LINE('受信アラート: ' || v_name || ' / メッセージ: ' || v_message);
END IF;
DBMS_ALERT.REMOVEALL;
END;
/
まとめ
- DBMS_PIPE:セッション間のメッセージパッシング。PACK_MESSAGE で積んで SEND_MESSAGE で送信、RECEIVE_MESSAGE で受信。トランザクションと無関係に動作する
- DBMS_ALERT:COMMIT 連動のイベント通知。REGISTER でサブスクライブ、SIGNAL で通知、WAITONE / WAITANY で待機。ROLLBACK ではシグナルが発火しない
- 使い分け:1対1のメッセージ送受信には DBMS_PIPE、DML 完了後のイベント通知・1対多通知には DBMS_ALERT が向いている
- DBMS_PIPE の注意:プライベートパイプは所有者のみアクセス可能。使用後は REMOVE_PIPE でパイプを削除してリソースを解放する
- DBMS_ALERT の注意:セッション終了時に REMOVE / REMOVEALL でアラート登録を解除する
セッション間の排他制御には DBMS_LOCK完全ガイドを参照してください。DBMS_SCHEDULER でジョブを非同期実行する方法は DBMS_SCHEDULER完全ガイドも参照してください。