我可以从 dbms_scheduler 作业中以某种方式获取返回值吗?

Posted

技术标签:

【中文标题】我可以从 dbms_scheduler 作业中以某种方式获取返回值吗?【英文标题】:Can I get a return value in some way from a dbms_scheduler job? 【发布时间】:2014-01-21 20:31:31 【问题描述】:

我有一个网页(通过 PL/SQL 生成),允许某人打开或关闭远程设备。他们会看到一个设备列表,他们使用复选框来选择要切换的设备。 UTL_HTTP 用于与设备通信。目前,设备是串行切换的。一旦全部切换完毕,就会向用户发送一封电子邮件。根据选择的设备数量,连续执行此操作可能需要很长时间。所以我正在考虑使用 DBMS_SCHEDULER 来并行执行切换。

问题是切换过程返回一个状态,要么是“OK”,要么是失败的原因。我需要将该结果包含在给用户的电子邮件中。因此,我需要“主要”过程来创建 SCHEDULER 作业,然后等待它们完成(并以某种方式获取它们的状态),然后再向用户发送电子邮件。

这可能吗,除了让每个作业将其状态写入由“主”进程轮询的表中吗?我已经阅读了对 DBMS_PIPE 进行进程间通信的引用,但没有找到一个很好的例子(即对我有意义的例子)来说明如何做到这一点。

【问题讨论】:

看看DBMS_AQ... 可以通过引发异常并从DBA_SCHEDULER_JOB_RUN_DETAILS 读取它来传递一些信息。但这比写到一张桌子上要丑得多。 我最终让每个作业将其状态写入表,并让主进程轮询该表,直到所有作业完成。不过,我不确定我是否应该将其发布为“答案”。 我认为值得将其作为答案。将来,如果有人想从 dbms_scheduler 获取返回值,知道答案是“不,它不能完成”会很有用。 【参考方案1】:

如果有办法做到这一点,我想不通。我最终让每项工作都将其状态写入表格。主进程知道创建了多少个单独的作业,并轮询表以了解所有作业何时完成(或者在经过指定的时间后超时,以防其中一个作业因某种原因死亡)。

【讨论】:

【参考方案2】:

观察并行进程通过 DBMS_SCHEDULER 作业运行的替代解决方案

新编辑:核心问题的简要讨论

(编辑:2014 年 3 月 10 日)在观看此帖子的一位发帖者提供了一些有用的反馈后,我添加了此讨论。

开场评论:这里的其他讨论线程提到使用函数调用本身的一些输出值。这在现有的DBMS_SCHEDULER 功能中是不可能的。

没有OUT 类型参数值或函数输出与通知调用的过程遇到的条件相关。手头最紧迫的问题是:我们如何通过 PL/SQL 存储过程并行运行一系列相关任务?(即,在每个开始之前无需等待彼此完成。)

任何调用过程任务的东西都不应等待状态输出。响应时间可能会有很大的变化,并且以这种方式调用该过程的任何东西也会挂起。相关进程将等待过程完成或指定输出的返回。

建议的方法:关于这个问题的其他 cmet 都在正确的轨道上。将自定义输出写入表中,以便稍后在响应准备好时对其进行查询。如果您真的想让这成为一个不干涉的任务,请尝试在输出表上放置一个触发器。每次填充特定值的消息(表示请求的已完成状态)时,使用 Oracle 的 Mail 程序包调用一个过程,以发送您的通知电子邮件。

如何通过了解 DBMS_SCHEDULER 功能来跟踪调用的作业

使用计划作业是一种很好的方式来启动和监视一组不依序相互依赖的过程调用。我已经能够使用以前版本的 Oracle 数据库的原始 DBMS_JOB 功能完成类似的方法。

案例研究:使用基于 Web 的应用程序界面 (Oracle Application Express) 我有一个项目允许用户启动一系列资源密集型数据库操作。只需发起请求即可。

实际使用场景:用户无需等待完成。问题在于,将 Web 请求表单直接连接到对该数据库包及其过程的调用还束缚了对表单及其会话的控制,使用户“等待”过程本身完成。

启动调用此过程的计划作业将与网页的交互与等待实际过程完成分开。调度作业任务几乎是瞬间完成的,因此从提交请求到将控制权返回给网页之间的等待时间也可以忽略不计。

使用 Oracle DBMS_SCHEDULER:方法简介

讨论中的当前问题和解决方案:使用原生 DBMS_SCHEDULER 状态视图来监控流程的进度。有很多,但 ALL_SCHEDULER_JOB_LOG 是最简单的集合,是我们努力完成的一个良好开端。

    为每项工作使用易于识别的名称...以及可能相互关联的每项工作。 启动一个额外的作业来监视所有并行任务,直到最后一个任务完成。一旦满足此条件,请更改此“监视”作业以结束。

在调度程序上启动数据库作业的基本语法

过程DBMS_SCHEDULER.CREATE_JOB 在一次调用中创建一个作业,而不使用现有的程序或计划:

DBMS_SCHEDULER.CREATE_JOB (
   job_name             IN VARCHAR2,
   job_type             IN VARCHAR2,
   job_action           IN VARCHAR2,
   number_of_arguments  IN PLS_INTEGER              DEFAULT 0,
   start_date           IN TIMESTAMP WITH TIME ZONE DEFAULT NULL,
   repeat_interval      IN VARCHAR2                 DEFAULT NULL,
   end_date             IN TIMESTAMP WITH TIME ZONE DEFAULT NULL,
   job_class            IN VARCHAR2                 DEFAULT 'DEFAULT_JOB_CLASS',
   enabled              IN BOOLEAN                  DEFAULT FALSE,
   auto_drop            IN BOOLEAN                  DEFAULT TRUE,
   comments             IN VARCHAR2                 DEFAULT NULL);

这些是您应该密切注意的输入参数:

job_name 您可以将其保留为默认值,也可以使用一致的命名约定来帮助组织您的工作请求。 JOB_NAME 是一个特殊参数,因为它有一个名为 *GENERATE_JOB_NAME* 的辅助函数,您可以在其中指定一个命名前缀以与内部名称分配相结合。这不是绝对必要的,但它会有所帮助。

DBMS_SCHEDULER.GENERATE_JOB_NAME 
    (prefix IN VARCHAR2 DEFAULT 'JOB$_') RETURN VARCHAR2;

create_job 定义中的示例调用:

job_name => dbms_scheduler.generate_job_name( prefix => 'MY_EXAMPLE_JOB_')

所以在上面的示例中,我们可以有一系列的作业,其名称如下:MY_EXAMPLE_JOB_0001、MY_EXAMPLE_JOB_0002、MY_EXAMPLE_JOB_0003...

job_type 这是直接来自 Oracle 文档的。很可能是以下类型:*plsql_block*(该值也可能区分大小写)。

repeat_interval 不要为一次性并行任务设置此值。一旦引用的存储过程完成或出错,该作业将自己标识为已完成。

end_date 将此保留为空或未分配。此值不适用于它正在监视的过程的一次性执行。

start_date 将此保留为空或未分配。无值表示只要作业ENABLED就启动指定的作业。

启用默认为 FALSE,您需要在创建作业后立即将其设置为 TRUE,或者当您'准备启动进程“线程”。

auto_drop 这是一个重要的。此方法的其余部分取决于 DBMS_SCHEDULER 日志表中剩余的每个作业的元数据,即使它们遇到异常或完成。将此设置为 FALSE

job_action 这取决于您启动的并行进程的数量。首先,您应该启动您的第一个并行流程......以及相关的“监控”流程,该流程将对特定请求处于活动状态。 plsql_block 类型作业的作业操作如下所示:

示例 PL/SQL 块: 开始 my_procedure(a, b, c);结尾;

创建作业监控流程

您遇到的部分问题是 DBMS_SCHEDULER 可能会监视一个执行时间不同的进程,但它不能很好地让您知道它何时完成或是否遇到异常。

您的“观察者”进程只需是另一个计划作业,它查询ALL_SCHEDULER_JOB_LOG 表以了解它负责的过程,并确定它们是否都达到了所需的关闭状态。

假设:对于给定的请求,您将知道完成此类请求所需的并行进程数(远程启动的切换事件)......所有进程不必完全同时启动,但是观察者需要知道它仍然需要等待,即使它可以看到的所有相关进程都符合其“已完成”的条件。

您的“观察”程序需要包括的任务类型:

WATCHING SQL Criteria Example:

WITH   MONITOR_QUERY AS (
       SELECT COUNT(LOG_ID) AS COMPLETED_PROCESS_COUNT
         FROM ALL_SCHEDULER_JOB_LOG
        WHERE JOB_NAME LIKE '001-REQUEST%')

SELECT CASE WHEN COMPLETED_PROCESS_COUNT = <TOTAL_PROCESSES>
            THEN 'DONE' ELSE 'IN-PROGRESS' END as REQUEST_STATUS
  FROM MONITOR_QUERY

另请注意,当您调用运行监控进程的作业时,您可能会发现在开始其重复作业之前提前生成一个唯一的作业名称很有用(每个请求或一组并行作业只应执行一次):

DECLARE
   who_am_i   VARCHAR2(65);

BEGIN
   SELECT dbms_scheduler.generate_job_name
     INTO who_am_i
     FROM DUAL;

   --
   DBMS_SCHEDULER.CREATE_JOB (job_name => who_am_i,
     job_type => 'plsql_block',
     job_action => 'BEGIN my_monitoring_task(p_1, p_2, who_am_i); END',
     repeat_interval => 300,
     comments => 'Interval time units are defaulted to SECONDS';
     ...);

END;

如果在启动系列中的第一个并行作业的同时或不久之后创建此作业,则该作业最有效。

当监控请求完成时

当满足选择条件(即所有相关进程都以某种方式关闭)时,就该触发您的通知并停止此请求的观察程序了。

Stopping the Monitoring Job

DBMS_SCHEDULER.STOP_JOB (
   job_name         IN VARCHAR2
   force            IN BOOLEAN DEFAULT FALSE);    

job_name 如果您为启动的每个作业使用自定义命名方案,您还可以将此值作为输入参数存储到您的观察程序调用中。然后,观察者将知道如何在完成后自行关闭。请记住,如果您使用 GENERATE_JOB_NAME 函数调用,您只是为调度程序中使用的整个 job_name 指定前缀。

force 将此设置为 FALSE(默认值)或不指定。最好让 Oracle 想办法优雅地停止观察者工作。

结束的想法和评论

如果您的几个过程的结果或完成是相关的,则可以重复一个额外的计划作业作为监控“心跳”,以检查是否满足离散流程的所有依赖关系。

关于清理的评论:此设计需要将 *auto_drop* 参数设置为 FALSE。也可以安排每天或每周的进程来发出 *drop_job* 命令,该命令将清理调度程序的与已完成和报告的请求相关的记录日志。

您还可以看到,通过在计划作业本身中包含调用 *job_name*,您可以提供其中包含的过程在满足正确条件后自行关闭的能力。

【讨论】:

这并不能真正回答问题。这不仅仅是关于如何运行作业,而是如何从中获取价值。 @jonearles:计划作业是一个可行的解决方案......在解决方案的中途,我确定了作业计划程序日志视图以及如何查询它以识别其中包含的过程调用的状态。 是的,但这只会告诉你工作是否完成。它不会让您返回自定义值。 感谢您的反馈。我在我的解决方案的顶部回顾了一些额外的解释来澄清。如果您有时间阅读,您会发现我们在某些方面谈论的是同一件事:DBMS_SCHEDULER 没有输出值。写入外部表是记录来自被调用过程的自定义反馈/异常/警告/公告的方法。调度程序的元日志表也包含信息,因此请注意不要开始多余的工作。我对作业的讨论涉及从单个请求运行相同过程的多个实例的概念。【参考方案3】:

高级队列助您一臂之力。 从属会话(作业)准备好后,将它们的返回值放入 AQ(实际上允许任何数据结构)。 启动从属服务器的协调器会话监听队列并收集返回值。 实际上,无论如何,AQ 都是 Oracle 中推荐的会话间通信方式。

【讨论】:

【参考方案4】:

在 Oracle 12c 中,ALL_SCHEDULER_JOB_RUN_DETAILS.OUTPUT 列可用于从作业返回值。

例如,使用DBMS_OUTPUT 创建作业并写入输出:

begin
    dbms_scheduler.create_job(
        job_name => 'TEST_JOB',
        job_type => 'PLSQL_BLOCK',
        job_action => q'[begin dbms_output.put_line('Test output'); end; ]',
        enabled => true
    );
end;
/

现在读取输出:

select job_name, to_char(log_date, 'YYYY-MM-DD') log_date, output
from all_scheduler_job_run_details
where owner  = user
    and job_name = 'TEST_JOB'
order by log_date desc;

JOB_NAME   LOG_DATE     OUTPUT
--------   --------     -------
TEST_JOB   2017-12-26   Test output

【讨论】:

【参考方案5】:

如果您能够使用 oracle 版本 11,则可以使用 DBMS_PARALLEL_EXECUTE pl/sql 包,它可以满足您的需求。如果您无法升级,那么您可以从 pl/sql 中实现一些提供类似功能的 c 标注。

如果您决定使用 dbms_pipe 并且正在使用 RAC 数据库选项,请注意使用 DBMS_PIPE 在故障转移方面存在局限性。

【讨论】:

v11 不是一个选项;卡在 10g 上。

以上是关于我可以从 dbms_scheduler 作业中以某种方式获取返回值吗?的主要内容,如果未能解决你的问题,请参考以下文章

Oracle DBMS_SCHEDULER sysdate 参数

如何在使用 DBMS_SCHEDULER.CREATE_JOB 创建的作业中运行复杂的 PL/SQL 代码

带有 OUT 参数调用的 Oracle DBMS_SCHEDULER.create_job

DBMS_SCHEDULER 下次运行日期

执行 Java 的 Oracle 作业

在python中以某列为分组对象还能对某列进行依次排序吗