[Oracle10G新特性]_19.调度程序
这个属性还真的是不错,之前没有注意过。特别是关于执行操作系统程序和时间定义语法的更改,非常不错。之前想要在一天的某2个时间执行,都需要定义两个job才可以,原来确实是可以通过decode函数来达到这个目的的,不过知道dbms_scheduler包的功能之后,就更加简单了。总之这个又是一个10g的改进之处,非常不错。考虑一下直接应用。
-----------------------------------------------------------------------------
调度程序
您是否厌倦了在 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
数字前面的负号指示从月底算起,而不是从月初算起。
如果您想要验证时间间隔设置是否正确,那应该怎么办? 看看从日历字符串中构造的各个日期不是很好吗? 好的,您可以使用 EVALUATE_CALENDAR_STRING 过程来预览接下来的日期的计算。 利用第一个例子 — 从星期一到星期五每天在上午 7:00 和下午 3:00 运行 Statspack — 您可以按如下方式检查您的时间间隔字符串的准确性:
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" 标题下找到(显示在图中的一个红色椭圆中)。
图 1: Administration 页面
所有与调度程序相关的任务(如创建、删除和维护作业)都能够通过该页面中的超链接任务轻松完成。 让我们看看这些任务中的一部分。 我们在早些时候创建了所有这些任务,因此单击 Jobs 标签将显示一个类似于图 2 的屏幕。
图 2: 计划作业
单击 COLLECT_STATS 作业允许您修改它的属性。 当您单击 "Job Name" 时,将出现图 3 中显示的屏幕。
图 3: 作业参数
正如您所看到的,您可以通过单击相应的标签来修改作业的参数以及计划和选项。 在完成所有的修改之后,您可以按下 "Apply" 键,使修改变为永久性的。 在这么做之前,您可能想单击标记为 "Show SQL" 的按钮,它显示将执行的确切的 SQL 语句 — 即使原因只是要查看调用了哪些 API,从而使您能够了解幕后的工作。 您还可以将 SQL 存储在一个脚本中,并在以后执行它,或者将它存储为一个模板,以便将来使用。
结论
Oracle 数据库 10g 中的调度程序是从原来的 dbms_job 接口实现的巨大的飞跃。