dbms_aq.dequeue在回调中必须设wait := 0,否则因阻塞aq通知线程导致后续消息无法投递;需显式指定consumer_name并捕获no_data_found异常,且不可重试。

DBMS_AQ.DEQUEUE 必须设 wait := 0 否则回调会卡死
Oracle 高级队列(AQ)的回调过程是后台异步调用,不是普通 PL/SQL 会话。一旦在回调里用 DBMS_AQ.DEQUEUE 默认参数(即隐式 wait := DBMS_AQ.FOREVER),整个 AQ 通知线程会被阻塞,后续所有消息都无法投递,表现为“注册了但没触发”或“只触发一次就停了”。
正确做法是显式设置非阻塞模式,并捕获 NO_DATA_FOUND:
l_dequeue_options.wait := 0; l_dequeue_options.consumer_name := 'MY_CONSUMER'; DBMS_AQ.DEQUEUE( queue_name => 'SCOTT.MY_QUEUE', dequeue_options => l_dequeue_options, message_properties => l_message_properties, payload => l_message, msgid => l_message_handle ); EXCEPTION WHEN NO_DATA_FOUND THEN NULL;
- 不设
consumer_name可能取到其他 consumer 的消息,尤其多消费者队列下极易错乱 -
wait := 0是硬性要求,不是可选项;哪怕队列肯定有消息,也不能依赖“刚好有” - 别在回调里重试 dequeue —— 没必要,AQ 本身会按 schedule 重复通知,反复调用只会增加失败概率
DBMS_AQ.REGISTER 注册失败的三大硬伤
注册后回调完全不触发,90% 是以下三个条件没同时满足:
-
namespace字段必须设为DBMS_AQ.NAMESPACE_AQ,写成NULL、0或漏掉都会静默失败 -
callback值必须是带 schema 的合法全名,例如'SCOTT.AQ_CALLBACK_PROC';不能带括号、不能是匿名块、不能是函数、不能省略 owner - 执行
DBMS_AQ.REGISTER的用户,必须对目标回调过程有EXECUTE权限,且该过程签名严格匹配:PROCEDURE p (context RAW, reginfo SYS.AQ$_REG_INFO)
检查是否注册成功,别只看 PL/SQL 是否报错,要查:
SELECT * FROM DBA_QUEUE_SCHEDULES WHERE queue_name = 'MY_QUEUE'; —— 状态应为 ENABLED
SELECT * FROM V$AQ_AGENT; —— 确认 agent 已上线且无 ERROR 标记
队列停不下来?STOP_QUEUE 不等于“立刻冻结”
DBMS_AQ.STOP_QUEUE 实际作用很有限:它只阻止新入队(ENQUEUE)和新出队(DEQUEUE)操作启动,但不会中断已在运行的 dequeue 事务,也不会回滚已取出但未提交的消息处理逻辑。
- 如果存在长时间运行的
DEQUEUE(比如wait := 300),调用STOP_QUEUE后它仍会继续执行完 - 查
V$AQ视图,若WAITING> 0,说明还有消息等着被消费,得先确认消费者是否异常退出 - 若启用了传播(propagation),
DBA_QUEUE_SCHEDULES中残留的调度记录会导致队列看似“停不住”,必须先DBMS_AQADM.DROP_QUEUE_SCHEDULE - 已用
DBMS_AQ.REGISTER注册的监听器不会随队列停止而注销,需手动DBMS_AQ.UNREGISTER,否则可能触发已停队列的虚假回调
队列消息体必须用 OBJECT TYPE 定义,且需显式授权
AQ 不支持直接用 VARCHAR2 或 NUMBER 作 payload,必须先创建一个 OBJECT TYPE,再在 CREATE_QUEUE_TABLE 中引用它。
常见错误是建好 type 后忘了授权:
CREATE OR REPLACE TYPE task_c AS OBJECT ( taskid NUMBER, payload VARCHAR2(4000) );
然后必须执行:
GRANT EXECUTE ON task_c TO aq_user_plsql;
- 没这句授权,
DBMS_AQ.ENQUEUE会报ORA-06550 / PLS-00302类型不可访问错误 - type 必须无 body(即不能有 member procedure/function),否则
CREATE_QUEUE_TABLE会失败 - 如果用 Java 或 ODP.NET 消费该队列,type 名称大小写必须与数据库中定义完全一致,否则反序列化失败
真正难调试的点往往不在代码逻辑,而在这些隐式依赖:namespace 值写错一个字母、schema 名少打一个字母、type 少授一次权、回调过程里多写了一个分号——全都会导致“看起来什么都没做错,但就是不工作”。











