分类: Oracle
2008-07-30 15:44:32
您是否厌倦了在 dbms_job 中手工管理时间间隔的繁琐,需要数据库内部有个新调度系统? 让我们就着眼于数据库本身来解决这个问题。
你们中的一部分人可能广泛使用 dbms_job 程序包来将数据库作业提交到后台运行,控制运行的时间或时间间隔,报告故障等等。 然而,我感觉你们中的大部分人不会这么做。
这个程序包存在的问题是它只能够处理 PL/SQL 代码段 — 仅能处理匿名程序块和存储程序单元。 它不能在数据库外部处理操作系统命令文件或可执行文件中的任何东西。 为此,您将不得不求助于操作系统调度实用工具(如 Unix 中的 cron 或 Windows 中的 AT 命令)。 或者,您可以使用甚至可能通过提供图形用户界面来扩展这种功能的第三方工具。 虽然如此,dbms_job 有一个超过这些替代方法的独特优点: 它只有在数据库启动并运行时才有效。 如果数据库关闭,则作业不会运行。 数据库外部的工具必须人工检查数据库是否启动 — 而这可能很困难。 另一个优点是 dbms_job 在数据库内部,因此您可以通过数据库访问实用工具(如 SQL*Plus)来访问它。 Oracle 数据库 10g 调度程序特性提供了各方面的好处: 它是直接在数据库内部的一个作业调度实用程序,强大到足够处理所有类型的作业,而不只是 PL/SQL 代码段。 最好的一点是,它是数据库自带的,无需任何额外的成本。 在这一部分中,我们将看看它是如何工作的。 将作业与程序连接 也许最好通过一个例子来介绍这个概念。 假定您创建了一个 shell 脚本,以将存档的日志文件转移到一个不同的文件系统中,如下:/home/arup/dbtools/move_arcs.sh首先,您需要使数据库知道这个脚本是一个要在作业中使用的程序。 要创建这个程序,您必须拥有 CREATE JOB 权限。
begin dbms_scheduler.create_program ( program_name => 'MOVE_ARCS', program_type => 'EXECUTABLE', program_action => '/home/arup/dbtools/move_arcs.sh', enabled => TRUE, comments => 'Moving Archived Logs to Staging Directory' ); end; /这里您创建了一个命名程序单元,将其指定为可执行文件。注意这个程序单元叫什么。 接下来,您将创建一个每 30 分钟运行一次的命名计划,该计划的名称为 EVERY_30_MINS。 您将使用以下命令来完成这一操作:
begin dbms_scheduler.create_schedule ( schedule_name => 'EVERY_30_MINS', repeat_interval => 'FREQ=MINUTELY; INTERVAL=30', comments => 'Every 30-mins' ); end; /现在创建了程序和计划,接着您将把程序与计划连接来创建作业。
begin dbms_scheduler.create_job ( job_name => 'ARC_MOVE', program_name => 'MOVE_ARCS', schedule_name => 'EVERY_30_MINS', comments => 'Move Archived Logs to a Different Directory', enabled => TRUE ); end; /这将创建一个每 30 分钟运行一次的作业,该作业执行 shell 脚本 move_arcs.sh。它将由数据库内部的调度程序特性来处理 — 无需 cron 或 AT 实用工具。 创建没有程序的作业 在上述情况下,您创建了一个程序来引用一个操作系统实用工具或程序,并创建了一个计划来指定运行的次数,最后将这两者连接起来创建作业。 不过,不需要从程序中创建作业;也可以独立地定义它们。 例如,您可以创建上述作业,而无需首先创建一个程序。
begin dbms_scheduler.create_job ( job_name => 'ARC_MOVE_2', schedule_name => 'EVERY_30_MINS', job_type => 'EXECUTABLE', job_action => '/home/arup/dbtools/move_arcs.sh', enabled => true, comments => 'Move Archived Logs to a Different Directory' ); end; /这里直接指定 OS 可执行文件,而无需首先将其创建为一个程序。 同样,您可以创建一个作业,而无需一个命名的计划。
begin dbms_scheduler.create_job ( job_name => 'ARC_MOVE_3', job_type => 'EXECUTABLE', job_action => '/home/arup/dbtools/move_arcs.sh', repeat_interval => 'FREQ=MINUTELY; INTERVAL=30', enabled => true, comments => 'Move Archived Logs to a Different Directory' ); end; /Scheduler 超过 dbms_job 的一个优点从我们最初的例子中可以很清楚地看出来: 能够调用 OS 实用工具和程序,而不只是 PL/SQL 程序单元。 这一能力使它成为管理 Oracle 数据库和相关作业的最全面的作业管理工具。
不过,您可能已经注意到了另一个同样重要的优点: 能够以自然语言定义时间间隔。 注意在上面的例子中,我们要我们的计划每 30 分钟运行一次,因此通过一个简单的类似英语的表达式(而不是 PL/SQL)定义了 REPEAT_INTERVAL 参数:
'FREQ=MINUTELY; INTERVAL=30'
一个更复杂的例子甚至可以更好地帮助说明这一优点。 假定您的生产应用程序在上午 7:00 和下午 3:00 变得最活跃,为了收集系统统计数据,您想从星期一到星期五仅在上午 7:00 和下午 3:00 运行 Statspack。 如果您使用 DBMS_JOB.SUBMIT 来创建一个作业,那么 NEXT_DATE 参数将看起来像这样:
DECODE ( SIGN ( 15 - TO_CHAR(SYSDATE,'HH24') ), 1, TRUNC(SYSDATE)+15/24, TRUNC ( SYSDATE + DECODE ( TO_CHAR(SYSDATE,'D'), 6, 3, 1 ) ) +7/24 )这种代码容易理解吗?实际上不容易。 现在让我们看看 DBMS_SCHEDULER 中的等价的作业。 REPEAT_INTERVAL 参数将像下面这么简单:
'FREQ=DAILY; BYDAY=MON,TUE,WED,THU,FRI; BYHOUR=7,15'此外,这个参数值可以接收各种时间间隔,它们中的一些非常强大。 下面是更多的一些例子:
FREQ=MONTHLY; BYDAY=-1SUN
FREQ=MONTHLY; BYDAY=3FRI
FREQ=MONTHLY; BYDAY=-2FRI
set serveroutput on size 999999 declare L_start_date TIMESTAMP; l_next_date TIMESTAMP; l_return_date TIMESTAMP; begin l_start_date := trunc(SYSTIMESTAMP); l_return_date := l_start_date; for ctr in 1..10 loop dbms_scheduler.evaluate_calendar_string( 'FREQ=DAILY; BYDAY=MON,TUE,WED,THU,FRI; BYHOUR=7,15', l_start_date, l_return_date, l_next_date ); dbms_output.put_line('Next Run on: ' || to_char(l_next_date,'mm/dd/yyyy hh24:mi:ss') ); l_return_date := l_next_date; end loop; end; /输出结果如下:
Next Run on: 03/22/2004 07:00:00 Next Run on: 03/22/2004 15:00:00 Next Run on: 03/23/2004 07:00:00 Next Run on: 03/23/2004 15:00:00 Next Run on: 03/24/2004 07:00:00 Next Run on: 03/24/2004 15:00:00 Next Run on: 03/25/2004 07:00:00 Next Run on: 03/25/2004 15:00:00 Next Run on: 03/26/2004 07:00:00 Next Run on: 03/26/2004 15:00:00这确认您的设置是正确的。 类、计划和窗口 一个称职的好的作业调度系统必须支持为作业安排优先级的能力。 例如,统计数据收集作业突然进入 OLTP 工作负载窗口,从而可能影响那里的性能。 为了确保统计数据收集作业不消耗资源从而影响 OLTP,您可以使用作业类、资源计划和调度程序窗口。 例如,当定义一个作业时,您可以使之属于一个作业类,作业类映射到一个资源使用组上,以进行资源分配。 为此,首先您需要定义一个名称为(比如说) OLTP_GROUP 的资源使用组。
begin dbms_resource_manager.clear_pending_area(); dbms_resource_manager.create_pending_area(); dbms_resource_manager.create_consumer_group ( consumer_group => 'oltp_group', comment => 'OLTP Activity Group' ); dbms_resource_manager.submit_pending_area(); end; /接下来,您需要创建一个资源计划。
begin dbms_resource_manager.clear_pending_area(); dbms_resource_manager.create_pending_area(); dbms_resource_manager.create_plan ('OLTP_PLAN', 'OLTP Database Activity Plan'); dbms_resource_manager.create_plan_directive( plan => 'OLTP_PLAN', group_or_subplan => 'OLTP_GROUP', comment => 'This is the OLTP Plan', cpu_p1 => 80, cpu_p2 => NULL, cpu_p3 => NULL, cpu_p4 => NULL, cpu_p5 => NULL, cpu_p6 => NULL, cpu_p7 => NULL, cpu_p8 => NULL, parallel_degree_limit_p1 => 4, active_sess_pool_p1 => NULL, queueing_p1 => NULL, switch_group => 'OTHER_GROUPS', switch_time => 10, switch_estimate => true, max_est_exec_time => 10, undo_pool => 500, max_idle_time => NULL, max_idle_blocker_time => NULL, switch_time_in_call => NULL ); dbms_resource_manager.create_plan_directive( plan => 'OLTP_PLAN', group_or_subplan => 'OTHER_GROUPS', comment => NULL, cpu_p1 => 20, cpu_p2 => NULL, cpu_p3 => NULL, cpu_p4 => NULL, cpu_p5 => NULL, cpu_p6 => NULL, cpu_p7 => NULL, cpu_p8 => NULL, parallel_degree_limit_p1 => 0, active_sess_pool_p1 => 0, queueing_p1 => 0, switch_group => NULL, switch_time => NULL, switch_estimate => false, max_est_exec_time => 0, undo_pool => 10, max_idle_time => NULL, max_idle_blocker_time => NULL, switch_time_in_call => NULL ); dbms_resource_manager.submit_pending_area(); end; /最后,您利用之前创建的资源使用组来创建一个作业类。
begin dbms_scheduler.create_job_class( job_class_name => 'OLTP_JOBS', logging_level => DBMS_SCHEDULER.LOGGING_FULL, log_history => 45, resource_consumer_group => 'OLTP_GROUP', comments => 'OLTP Related Jobs' ); end; /让我们看看这个调用中的各个参数。 LOGGING_LEVEL 参数设置为作业类跟踪多少日志数据。 设置 LOGGING_FULL 指示这个类中的作业上的所有活动 — 创建、删除、运行、更替等等 — 都将被记录在日志中。 这些日志可以从 DBA_SCHEDULER_JOB_LOG 视图中看到,并保留 45 天(在 LOG_HISTORY 参数中指定,默认值是 30 天)。 还指定了与这个类相连的资源使用组。 这些作业类可以从 DBA_SCHEDULER_JOB_CLASSES 视图中看到。 当您创建一个作业时,您可以随意地将其与一个类关联。 例如,当创建 COLLECT_STATS 时,对于一个通过执行一个存储过程来收集优化程序统计数据的作业 collect_opt_stats(),您可能已经指定了:
begin dbms_scheduler.create_job ( job_name => 'COLLECT_STATS', job_type => 'STORED_PROCEDURE', job_action => 'collect_opt_stats', job_class => 'OLTP_JOBS', repeat_interval => 'FREQ=WEEKLY; INTERVAL=1', enabled => true, comments => 'Collect Optimizer Stats' ); end; /这些命令将把新创建的作业放在 OLTP_JOBS 类中,然后后者由资源计划 OLTP_GROUP 来管理,该资源计划将限制分配多少 CPU 给过程、在它被切换到一个不同的组之前的最大运行数、要切换到的组等等。 在这个类中定义的任意作业都将由相同的资源计划指令来管理。 这一功能对于防止不同类型的作业过度占用系统资源特别有用。 调度程序窗口是拥有一个相关的资源计划的时间帧,它用于激活该计划 — 从而在一个时间帧上支持作业的不同优先级。 例如,一些作业(如更新数据库以支持实时决策的批量程序)在白天需要高优先级但在晚上变为低优先级(反之亦然)。 您可以通过定义不同的资源计划,然后使用调度程序窗口激活它们来实施这种调度。 监视 在一个作业启动之后,您可以从 DBA_SCHEDULER_JOB_LOG 视图中监视它的状态,其中 STATUS 列显示了作业的当前状态。 如果它显示 FAILED,那么您可以进一步向下查看,以从 DBA_SCHEDULER_JOB_RUN_DETAILS 视图中找出原因。 管理 迄今为止,我们已经讨论了如何创建几种类型的对象: 程序、计划、作业类和作业。 如果您想修改它们中的一些,以调整适应不断变化的需求,那该怎么办? 好的,您可以通过 DBMS_SCHEDULER 程序包中提供的 API 来实现这一目的。 从企业管理器 10g 主页的 Database 标签中,单击 Administration 链接。 这将显示 Administration 屏幕(如图 1 所示)。所有与调度程序相关的任务都可以在右下角的 "Scheduler" 标题下找到(显示在图中的一个红色椭圆中)。