不能直接用enqueue/dequeue做异步监听,因其为同步阻塞调用,无法响应数据库级事件;真正异步监听须依赖dbms_aq.register原生通知机制,配合oracledependency绑定队列视图及符合约束的pl/sql回调过程。
为什么不能直接用 enqueue/dequeue 做异步监听
odp.net 的 oracleaqqueue.enqueue 和 oracleaqqueue.dequeue 都是同步阻塞调用,即使你把它包在 task.run 里,也只是把阻塞操作扔到线程池线程上——既不释放主线程资源,也无法响应数据库级的事件通知,还容易因超时或空队列反复轮询拖垮连接池。
真正的异步监听必须依赖 Oracle 数据库原生的 AQ 通知机制(DBMS_AQ.REGISTER),由数据库在消息入队时主动回调你的 .NET 程序。这要求你用 ODP.NET 的 OracleDependency + 自定义 PL/SQL 回调过程,而不是靠客户端轮询。
OracleDependency 怎么绑定到 AQ 队列
OracleDependency 本身不直接支持队列监听,它只监听 SQL 查询结果集变化。所以得绕一层:用一个视图包装队列内容,再让 OracleDependency 监听该视图。
- 先建一个队列表和队列(如
QUEUE_MAP_MESSAGE_TABLE/QUEUE_MAP_MESSAGE),并确保已START_QUEUE - 创建一个视图,例如:
CREATE OR REPLACE VIEW v_aq_pending AS SELECT msg_id, enq_time, state FROM USER_QUEUE_CONTENTS WHERE queue = 'QUEUE_MAP_MESSAGE' AND state = 0;
- 在 .NET 中用
OracleCommand执行SELECT * FROM v_aq_pending,再关联OracleDependency实例 - 注意:视图必须基于
USER_QUEUE_CONTENTS或ALL_QUEUE_CONTENTS,不能用DBA_*视图,否则权限不足会静默失败
PL/SQL 回调过程必须满足哪些条件
数据库端的回调过程不是普通存储过程,它受 AQ 限制,必须严格符合签名和事务约束,否则注册后根本不会触发。
- 过程必须声明为
AUTHID CURRENT_USER,且拥有EXECUTE权限 - 参数只能是
sys.aq$_reg_info数组和NUMBER,不能加其他参数 - 过程体内禁止 DML、DDL、COMMIT/ROLLBACK —— AQ 在自治事务中调用它,任何显式事务控制都会报
ORA-06519 - 典型结构:
CREATE OR REPLACE PROCEDURE my_aq_callback(regs sys.aq$_reg_info_array, p_num NUMBER) AUTHID CURRENT_USER IS BEGIN NULL; -- 实际逻辑应写入 DBMS_PIPE 或 UTL_HTTP 发送到 .NET 端 HTTP 接口 END;
- 注册命令:
DBMS_AQ.REGISTER(sys.aq$_reg_info_list(sys.aq$_reg_info('MY_SCHEMA.QUEUE_MAP_MESSAGE', DBMS_AQ.NAMESPACE_AQ, 'my_aq_callback', HEXTORAW('FF'))), 1);
为什么推荐用 DBMS_AQ.LISTEN 轮询替代纯依赖回调
纯 PL/SQL 回调在高并发或网络不稳定时容易丢失通知;而 DBMS_AQ.LISTEN 是数据库内置的轻量级监听,配合 .NET 的 Task.Delay 可控轮询,更可靠。
-
LISTEN不阻塞连接,返回NO_DATA_FOUND表示无新消息,不是错误 - 调用前需设置
wait := 1(秒)避免空转,但不要设成 0 —— 否则频繁调用会压垮AQ_TM_PROCESS - 必须用独立连接执行
LISTEN,不能复用业务连接,否则事务隔离问题会导致消息重复或丢失 - 示例 PL/SQL 块(供 .NET
OracleCommand调用):DECLARE l_waittime NUMBER := 1; l_agent sys.aq$_agent; l_msgid RAW(16); BEGIN DBMS_AQ.LISTEN(queue_name => 'MY_SCHEMA.QUEUE_MAP_MESSAGE', wait => l_waittime, agent => l_agent, msgid => l_msgid); :p_msgid := l_msgid; EXCEPTION WHEN NO_DATA_FOUND THEN :p_msgid := HEXTORAW(''); END;
真正难的不是写代码,而是让数据库和 .NET 进程在事务边界、连接生命周期、通知时效性之间达成一致——漏掉任意一环,消息就可能卡住或重复。











