背景動機
PL/SQL 設計初衷為強化 SQL 功能,原生不支援多執行緒/多進程。但在高併發批次處理、非同步作業協調、外部程式 (Pro*C、Java) 互動等場景,需實現會話間通信。Oracle 提供兩大內建套件:
- DBMS_PIPE:管道通信,支援雙向、異步、任意資料型別 (經打包)
- DBMS_ALERT:事件通知,支援廣播、同步 (Commit 觸發)、純文字訊息
DBMS_PIPE:管道通信機制
核心 API
| 函數/程序 | 用途 | 關鍵參數 |
PACK_MESSAGE(p_item IN VARCHAR2|NUMBER|DATE|RAW) | 將資料序列化進區域緩衝區 | 支援多次呼叫打包多欄位 |
SEND_MESSAGE(p_pipename IN VARCHAR2, p_timeout IN NUMBER DEFAULT 86400, p_maxwait IN NUMBER DEFAULT 86400) RETURN NUMBER | 發送緩衝區內容至指定管道 | 傳回 0=成功、1=逾時、2=緩衝區過大、3=管道不存在 |
RECEIVE_MESSAGE(p_pipename IN VARCHAR2, p_timeout IN NUMBER DEFAULT 86400) RETURN NUMBER | 從管道接收訊息至區域緩衝區 | 傳回碼同 SEND_MESSAGE |
UNPACK_MESSAGE(p_item OUT NOCOPY VARCHAR2|NUMBER|DATE|RAW) | 從區域緩衝區反序列化取出資料 | 須與 PACK 順序、型別完全對應 |
CREATE_PIPE(p_pipename IN VARCHAR2, p_maxpipesize IN NUMBER DEFAULT 8192, p_private IN BOOLEAN DEFAULT TRUE) RETURN NUMBER | 顯式建立管道 (隱式首次 SEND 也會建立) | 私有管道僅建立者可存取,公共管道需 EXECUTE 權限 |
REMOVE_PIPE(p_pipename IN VARCHAR2) RETURN NUMBER | 移除管道 | |
PURGE(p_pipename IN VARCHAR2) | 清空管道內所有訊息 | |
NEXT_ITEM_TYPE RETURN NUMBER | 檢查緩衝區下一項型別 | 9=VARCHAR2, 6=NUMBER, 12=DATE, 23=RAW |
雙會話通信完整範例
發送端 (Session A)
DECLARE
v_pipename VARCHAR2(30) := 'order_pipe';
v_status NUMBER;
v_order_id NUMBER := 12345;
v_amount NUMBER := 999.50;
v_note VARCHAR2(200) := 'Urgent VIP order';
BEGIN
DBMS_PIPE.PACK_MESSAGE(v_order_id);
DBMS_PIPE.PACK_MESSAGE(v_amount);
DBMS_PIPE.PACK_MESSAGE(v_note);
v_status := DBMS_PIPE.SEND_MESSAGE(v_pipename, 10); -- 10 秒逾時
IF v_status != 0 THEN
DBMS_OUTPUT.PUT_LINE('發送失敗,代碼: ' || v_status);
ELSE
DBMS_OUTPUT.PUT_LINE('訂單 ' || v_order_id ' 已送入管道');
END IF;
END;
/
接收端 (Session B)
DECLARE
v_pipename VARCHAR2(30) := 'order_pipe';
v_status NUMBER;
v_order_id NUMBER;
v_amount NUMBER;
v_note VARCHAR2(200);
BEGIN
LOOP
v_status := DBMS_PIPE.RECEIVE_MESSAGE(v_pipename, 30); -- 等待 30 秒
EXIT WHEN v_status != 0; -- 逾時或錯誤跳出
DBMS_PIPE.UNPACK_MESSAGE(v_order_id);
DBMS_PIPE.UNPACK_MESSAGE(v_amount);
DBMS_PIPE.UNPACK_MESSAGE(v_note);
DBMS_OUTPUT.PUT_LINE('處理訂單: ' || v_order_id || ', 金額: ' || v_amount || ', 備註: ' || v_note);
-- 實際業務邏輯: INSERT INTO orders ...; COMMIT;
END LOOP;
IF v_status = 1 THEN
DBMS_OUTPUT.PUT_LINE('等待逾時,無新訊息');
ELSIF v_status > 1 THEN
DBMS_OUTPUT.PUT_LINE('接收錯誤: ' || v_status);
END IF;
END;
/
進階用法:多資料型別、RAW、物件傳遞
-- 發送複雜結構
DBMS_PIPE.PACK_MESSAGE('HEADER'); -- 標識
DBMS_PIPE.PACK_MESSAGE(123); -- NUMBER
DBMS_PIPE.PACK_MESSAGE(SYSDATE); -- DATE
DBMS_PIPE.PACK_MESSAGE(UTL_RAW.CAST_TO_RAW('binary data')); -- RAW
DBMS_PIPE.PACK_MESSAGE(my_obj_type(1, 'test')); -- 物件型別 (需先定義 TYPE)
-- 接收端需按相同順序 UNPACK,並用 NEXT_ITEM_TYPE 防禦性檢查
DBMS_ALERT:事件通知機制
核心 API
| 程序 | 用途 | 關鍵參數 |
REGISTER(p_name IN VARCHAR2) | 註冊監聽指定警報名稱 | 同一會話可註冊多個警報 |
SIGNAL(p_name IN VARCHAR2, p_message IN VARCHAR2) | 發送警報訊息 (Commit 後才真正廣播) | 訊息長度 ≤ 1800 字元 |
WAITONE(p_name IN VARCHAR2, p_message OUT VARCHAR2, p_status OUT NUMBER, p_timeout IN NUMBER DEFAULT 86400) | 等待單一警報 (阻塞) | 狀態 0=收到、1=逾時 |
WAITANY(p_name OUT VARCHAR2, p_message OUT VARCHAR2, p_status OUT NUMBER, p_timeout IN NUMBER DEFAULT 86400) | 等待任一已註冊警報 | 回傳觸發的警報名稱 |
REMOVE(p_name IN VARCHAR2) | 移除註冊 | |
REMOVEALL | 移除所有註冊 | |
SET_DEFAULTS(p_sensitivity IN NUMBER DEFAULT 1.0) | 設定輪詢頻率 (秒) | 較小值回應快但 CPU 較高 |
警報通知範例
發送端
DECLARE
v_alert_name VARCHAR2(30) := 'inventory_alert';
BEGIN
-- 業務邏輯: 扣減庫存
UPDATE inventory SET qty = qty - 10 WHERE sku = 'ABC123';
IF SQL%ROWCOUNT = 0 THEN
DBMS_ALERT.SIGNAL(v_alert_name, 'SKU ABC123 庫存不足或不存在');
ELSIF (SELECT qty FROM inventory WHERE sku = 'ABC123') < 50 THEN
DBMS_ALERT.SIGNAL(v_alert_name, 'SKU ABC123 低庫存預警,剩餘: ' ||
(SELECT qty FROM inventory WHERE sku = 'ABC123'));
END IF;
COMMIT; -- 必須 COMMIT,SIGNAL 才會真正廣播
END;
/
接收端 (監聽程式,常駐後台)
DECLARE
v_alert_name VARCHAR2(30) := 'inventory_alert';
v_message VARCHAR2(1800);
v_status NUMBER;
BEGIN
DBMS_ALERT.REGISTER(v_alert_name);
DBMS_ALERT.SET_DEFAULTS(5); -- 每 5 秒輪詢一次
LOOP
DBMS_ALERT.WAITONE(v_alert_name, v_message, v_status, 60); -- 最長等 60 秒
IF v_status = 0 THEN
DBMS_OUTPUT.PUT_LINE('收到警報: ' || v_message);
-- 發送 Email、寫入日誌表、呼叫外部 API 等
-- 例如: UTL_MAIL.SEND(...); INSERT INTO alert_log VALUES (v_message, SYSDATE);
ELSIF v_status = 1 THEN
DBMS_OUTPUT.PUT_LINE('等待逾時,繼續監聽...');
ELSE
DBMS_OUTPUT.PUT_LINE('異常狀態: ' || v_status);
END IF;
END LOOP;
EXCEPTION
WHEN OTHERS THEN
DBMS_ALERT.REMOVEALL;
RAISE;
END;
/
DBMS_PIPE vs DBMS_ALERT 關鍵差異比較
| 維度 | DBMS_PIPE | DBMS_ALERT |
| 通信模式 | 點對點 (一對一),訊息被讀取後從管道移除 | 發布/訂閱 (一對多),訊息廣播給所有監聽者,純複製不移除 |
| 同步性 | 異步,不受交易 Commit/Rollback 影響 | 同步,SIGNAL 訊息必須 Commit 後才發送;Rollback 則不發送 |
| 資料型別 | 支援 VARCHAR2、NUMBER、DATE、RAW、物件型別 (經 PACK/UNPACK) | 僅支援 VARCHAR2 (≤1800 字元) |
| 訊息持久性 | 管道內訊息持久至讀取或 PURGE/REMOVE_PIPE | 無持久性,若無監聽者則訊息丟失 |
| 效能特徵 | 適合高頻、小訊息、雙向互動 | 適合低頻、事件通知、廣播場景 |
| 安全性 | 私有管道僅建立者存取,公共管道需 GRANT EXECUTE ON DBMS_PIPE | 需 GRANT EXECUTE ON DBMS_ALERT,任何註冊者均可接收 |
實務應用場景選型指南
| 場景 | 推薦方案 | 理由 |
| 批次作業分工:主程式分派任務給多個背景工作會話 | DBMS_PIPE | 點對點、確保每任務僅被一工作會話處理、支援複雜參數 |
| 資料異動觸發即時通知 (低庫存、異常交易、審核完成) | DBMS_ALERT | 事件驅動、一對多通知、Commit 語義符合業務邏輯 |
| PL/SQL 與 Pro*C / 外部程式雙向通信 | DBMS_PIPE | 支援 RAW/物件序列化、異步非阻塞 |
| 長時間作業進度回報 | DBMS_PIPE | 雙向、可定期發送進度、主程式隨時接收 |
| 多實例 RAC 環境跨節點通知 | DBMS_ALERT | RAC 環境下 ALERT 自動跨實例廣播 (PIPE 需額外設計) |
效能與限制注意事項
- 管道大小限制:預設 8KB,可建立時指定
MAXPIPESIZE (最大 32MB),訊息總長不可超過
- SGA 記憶體佔用:管道與警報訊息佔用 Shared Pool,大量併發需監控
V$SGASTAT 中 'pipe'、'alert' 區域
- 逾時設計:SEND/RECEIVE/WAIT 必須設定合理逾時,避免會話無限期阻塞
- 異常處理:接收迴圈需捕獲例外並
REMOVE_PIPE/REMOVEALL 清理資源
- 權限管理:
GRANT EXECUTE ON DBMS_PIPE TO app_user; 最小權限原則
- RAC 環境:DBMS_ALERT 自動跨實例,DBMS_PIPE 預設僅實例本地,跨實例需共享磁碟或自行實作
現代替代方案對比
| 方案 | 優點 | 缺點 | 適用性 |
| DBMS_PIPE / ALERT | 零外部依賴、資料庫內建、交易整合 | 功能有限、難以監控、不支援優先級/延遲佇列 | 輕量級、簡單協調、舊系統維護 |
| AQ (Advanced Queuing) | 企業級佇列、持久化、優先級、延遲、訂閱規則、JMS 相容 | 設定複雜、需額外授權 (EE)、管理負擔較重 | 正式訊息總線、高可靠、跨應用整合 |
| 外部 MQ (Kafka, RabbitMQ, ActiveMQ) | 功能豐富、生態成熟、語言無關、水平擴展 | 引入外部基礎設施、網路延遲、最終一致性 | 微服務架構、高吞吐、異構系統整合 |
| DBMS_SCHEDULER + 事件 | 排程驅動、資料庫原生、支援鏈式作業 | 非即時、輪詢間隔限制 | 定時批次、條件觸發作業 |
總結
DBMS_PIPE 與 DBMS_ALERT 是 PL/SQL 實現進程間通信的兩大基石,前者適合雙向、異步、結構化資料交換;後者適合事件驅動、廣播通知、Commit 語義綁定。選型時依據通信模式、資料複雜度、可靠性需求決定。現代系統建議逐步遷移至 Advanced Queuing 或外部訊息佇列,獲得更豐富的企業級特性與運維工具支援。