课程目标:
- 使用调度程序简化管理
- 创建作业,程序和调度
- 监控作业执行
- 使用基于时间和基于事件的调度执行调度程序作业
- 窗口,窗口组,作业类和使用者组
- 使用邮件通知
- 使用作业链执行一系列相关任务
- 在远程系统上的调度程序作业
- 使用高级调度程序概念来区分作业的优先级
1、简化管理
- 在每个月最后一天执行一系列月末任务
- 消息入队后立即运行出队过程
- 通过刷新物化视图复制表数据
- 运行每天的数据库备份任务
- 每天计算两次表和索引统计信息
- 文件到达文件系统后立即启动成批装入
- 每小时生成一个有关无效服务器访问尝试的报表
- 重建完当前索引后重建另一索引
Oracle 环境中的许多任务都需要作业调度功能。例行的数据库维护和应用程序逻辑要求定期调度并运行作业。企业到企业(B2B) 应用程序要求对其业务事件进行调度。DBA需要在指定时间窗口中调度日常维护作业。
Oracle数据库通过数据库调度程序提供高级调度功能,此调度程序是DBMS_SCHEDULER程序包中函数和过程的集合。可以在任何SQL 环境中,或者通过EM来调用此调度程序。
使用此调度程序,数据库管理员和应用程序开发者可以控制在数据库环境中执行各种任务的时间和位置。这些任务可能既耗时又复杂;可以使用调度程序来管理和计划这些任务。
可以根据时间或者在发生指定事件时启动调度程序作业,而且调度程序还可以在作业状态更改时(例如,从RUNNING变为COMPLETE)引发事件。还可以使用为达到组合目标而链接在一起的一系列已命名的程序。
2、核心组件
核心组件和主要步骤
一个作业包含两个必需组件:需要执行的操作,操作的发生时间或调度。“操作”是由命令区域和作业属性中的job_type和job_action参数表示的。“时间”是在调度中表示的,调度可以基于时间或事件,或者从属于其它作业的结果。
调度程序使用以下基本组件:
? “作业(job)”指定要执行的操作。它可以是PL/SQL过程、纯二进制可执行文件、Java应用程序或Shell脚本。可以将程序(内容)和调度(时间)指定为作业定义的一部分,也可以改用现有的程序或调度。可以使用作业的参数来定制其运行时行为。
? “调度(schedule)”指定作业的执行时间和次数。调度可以基于时间或事件。可以为作业定义调度,方法是使用一系列日期、一个事件,或两者相结合,以及表示重复间隔的附加说明。可以单独存储作业的调度,然后对多个作业使用同一个调度。
? “程序(program)”是有关特定可执行文件、脚本或过程的元数据集合。自动作业将执行某个任务。使用程序,无需修改作业本身即可修改作业任务或者“内容”。可以定义程序的参数,使用户可以修改任务的运行时行为。
3、基本工作流
使用调度程序来简化管理任务:
(1) 创建程序(启用或禁用)- 可选
– 在多个作业中重用此操作
– 在无需重新创建PL/SQL 块的情况下更改作业的调度
(2)创建并使用调度
(3)创建并提交作业
可以在EM的图形环境中执行所有步骤,或者通过命令行使用DBMS_SCHEDULER PL/SQL包执行所有步骤。
(1) 创建程序
使用CREATE_PROGRAM过程来创建程序。使用调度程序时,创建程序是一个可选部分。还可以对操作进行编码,使其在CREATE_JOB过程的匿名PL/SQL块中执行。通过单独创建程序,可以定义一次操作,然后在多个作业中重用此操作。使用这种方法,无须重新创建PL/SQL块即可更改作业的调度。
默认情况下,程序是以禁用状态创建的(除非enabled参数设为TRUE)。在将禁用的程序启用之前,作业无法执行此程序。可以通过将enabled的值指定为TRUE来指定应以启用状态创建程序。
(2)创建并使用调度
作业的调度可以是预定义的调度(用CREATE_SCHEDULE过程创建的),也可以是在创建作业时定义的。
调度指定有关作业运行的属性,例如:
? 起始时间,定义作业从哪一时间开始执行;结束时间,指定作业在哪一时间之后失效且不再进行调度
? 指定作业重复间隔的表达式
? 通过组合现有调度创建的复杂调度
? 启动作业之前必须满足的条件或状态变化(称为事件)
通过使用调度(而不是在作业定义中指定作业的执行次数),可以管理多个作业的预定执行,而无须更新多个作业定义。如果修改了某个调度,则使用该调度的每个作业都将自动使用新调度。
(3)创建并运行作业
作业是一个组合,其中包括调度、要执行的操作的说明以及作业需要的所有附加参数。可以为作业设置许多属性,用于控制作业的执行方式。
例子:创建作业
创建表和PL/SQL BLOCK
SQL> create table emprownum(time date,num number);
Table created.
SQL> insert into emprownum select sysdate,count(*) from employees;
1 row created.
SQL> select * from emprownum;
TIME NUM
------------------ ----------
20-AUG-16 107
declare
begin
insert into emprownum select sysdate,count(*) from employees;
end;
创建程序
BEGIN
DBMS_SCHEDULER.CREATE_PROGRAM(
program_name=>'"SYS"."EMPROWNUM"',
program_action=>'declare
begin
insert into emprownum select sysdate,count(*) from employees;
end;',
program_type=>'PLSQL_BLOCK',
number_of_arguments=>0,
comments=>'',
enabled=>TRUE);
END;
创建调度:
BEGIN
sys.dbms_scheduler.create_schedule(
repeat_interval=>'FREQ=HOURLY',
start_date=>systimestampattimezone'Asia/Shanghai',
end_date=>to_timestamp_tz('2016-08-21 17:50:00 Asia/Shanghai','YYYY-MM-DD HH24:MI:SS TZR'),
schedule_name=>'"SYS"."EMPROWNUM"');
END;
创建作业
BEGIN
sys.dbms_scheduler.create_job(
job_name=>'"SYS"."HREMPROWNUMJOB"',
program_name=>'"SYS"."EMPROWNUM"',
schedule_name=>'"SYS"."HREMPROWNUM"',
job_class=>'"DEFAULT_JOB_CLASS"',
auto_drop=>FALSE,
enabled=>TRUE);
END;
4、持久轻量作业
持久轻量作业:
? 减少启动作业所需的开销和时间
? 作业元数据和运行时数据在磁盘上占用很小的空间
? 是使用作业模板(在命令行中)创建的
BEGIN
DBMS_SCHEDULER.CREATE_JOB (
job_name => 'my_lightweight_job2',
program_name=>'"SYS"."EMPROWNUM"',
schedule_name=>'"SYS"."HREMPROWNUM"',
job_style => 'LIGHTWEIGHT');
END;
/
选择合适的作业类型:
–使用常规作业可提供最大灵活性。
–需要在很短的时间内创建大量作业时,请使用持久轻量作业。
轻量作业:
? 适合需要在一秒内创建数百个作业的客户。对于常规作业,每个作业都要创建一个数据库对象,用于描述作业、修改多个表以及生成重做。此种类型作业需求的相关开销是很大的。在Oracle数据库调度程序中,有一种“持久轻量作业”。轻量作业的目的是减少启动作业所需的开销和时间。将为作业创建极少的元数据。这可以减少启动作业时所需的时间和创建的重做。
? 作业元数据和运行时数据在磁盘上占用很小的空间。在磁盘上占用的空间小还可实现在RAC 环境中进行负载平衡。
? 始终是使用作业模板创建的。作业模板必须是一个存储过程或一个程序。存储过程可以保存作业所需的所有信息,包括权限。只可以指定少量作业属性:作业参数和调度。
? 必须在命令行中创建。JOB_STYLE参数在EM 中不可用。
在示例中,MY_PROG是作业模板,调度是通过已命名的调度应用的。
例子:使用前面创建的程序和调度创建轻量作业
SQL> BEGIN
2 DBMS_SCHEDULER.CREATE_JOB (
3 job_name => 'my_lightweight_job2',
4 program_name=>'"SYS"."EMPROWNUM"',
5 schedule_name=>'"SYS"."HREMPROWNUM"',
6 job_style => 'LIGHTWEIGHT');
7 END;
8 /
PL/SQL procedure successfully completed.
BEGIN
sys.dbms_scheduler.enable('"SYS"."MY_LIGHTWEIGHT_JOB2"');
END;
5、使用基于时间或者基于事件的调度
要为作业指定基于时间的调度,可以指定日历表达式或日期时间表达式。使用日历表达式时,将使用作业的重复间隔和起始日期来计算作业的下一启动时间。使用日期时间表达式时,指定的表达式确定作业下次应运行的时间。如果没有指定重复间隔,作业将只在指定的起始日期运行一次。
如果作业使用基于事件的调度,作业将在事件发生时运行。在较高层次上,可以将事件视为状态的更改。布尔条件的状态从FALSE更改为TRUE,或者从TRUE更改为FALSE时,将发生事件。
调度程序使用Oracle Streams Advanced Queuing (AQ)来引发和使用事件。
注:调度程序不保证作业恰好在计划的时间执行,因为可能会由于系统过载而造成资源不可用。
6、创建一个基于时间的任务
示例:创建一个作业,从今晚起在每晚11:00 调用备份脚本。
BEGIN
DBMS_SCHEDULER.CREATE_JOB(
job_name=>'HR.DO_BACKUP',
job_type=> 'EXECUTABLE',
job_action=>
'/home/usr/dba/rman/nightly_incr.sh',
start_date=> SYSDATE,
repeat_interval=>'FREQ=DAILY;BYHOUR=23',
/* next night at 11:00 PM */
comments => 'Nightly incremental backups');
END;
/
创建基于时间的作业
可以使用DBMS_SCHEDULER程序包的CREATE_JOB过程来创建作业。默认情况下将以禁用状态创建作业,仅当显式启用时,这些作业才生效并可以调度。所有作业名称都采用以下形式:[schema.]name。
应该使用SYSTIMESTAMP并指定时区,这样当时间因夏令时更改时,作业能够自动调整其执行时间。
默认情况下,将在当前模式中创建作业。可以通过指定模式的名称在另一个模式中创建作业,如示例所示。作业所有者是在其模式中创建作业的用户,而作业创建者是创建作业的用户。作业将按作业所有者的权限来执行。作业运行时的国家语言支持(NLS) 环境与创建作业时的环境相同。job_type参数指示作业将要执行的任务的类型。可能的值包括:
? PLSQL_BLOCK:匿名PL/SQL块
? STORED_PROCEDURE:命名的PL/SQL、Java 或外部过程
? EXECUTABLE:可以从操作系统(OS)命令行执行的命令
job_action参数可以是要运行的过程的名称、脚本的名称或操作系统命令的名称,也可以是匿名的PL/SQL代码块,具体取决于job_type参数的值。
在示例中,job_type被指定为EXECUTABLE,job_action是所需的外部可执行文件加上任何命令行参数(可选)的操作系统相关完整路径。
外部作业是指在数据库外部运行的作业。所有外部作业均作为低权限的来宾用户运行,这一点已在数据库管理员配置外部作业支持时确定。因为可执行文件作为低权限的来宾帐户运行,所以应确保其有权访问必要的文件和资源。大多数(但不是所有)平台都支持外部作业。对于不支持外部作业的平台,如果将作业或程序的属性创建或设置为EXECUTABLE类型,将返回错误。
例子:创建一个基于时间的作业
先创建一个脚本
[oracle@oeldb1 ~]$ cat /home/oracle/delarch.sh
#!/bin/bash
rman target / <<eof </eof<>
crosscheck archivelog all;
delete noprompt expired archivelog all;
exit
EOF
创建作业
BEGIN
sys.dbms_scheduler.create_job(
job_name=>'"SYS"."JOB1"',
job_type=>'EXECUTABLE',
job_action=>'/home/oracle/delarch.sh',
repeat_interval=>'FREQ=DAILY;BYHOUR=13;BYMINUTE=0;BYSECOND=0',
start_date=>systimestampattimezone'Asia/Shanghai',
end_date=>to_timestamp_tz('2016-08-24 Asia/Shanghai','YYYY-MM-DD TZR'),
job_class=>'"DEFAULT_JOB_CLASS"',
auto_drop=>FALSE,
enabled=>TRUE);
END;
7、创建一个基于事件的调度
要创建基于事件的作业,必须设置:
? 队列说明(应用程序将消息入队以启动作业)
? 一个事件条件(与Oracle Streams AQ规则条件的语法相同),如果为TRUE则启动作业
可以基于事件触发作业。应用程序可以通知调度程序启动作业,方法是将消息入队到Oracle Streams队列中。以这种方法启动的作业称为基于事件的作业。要创建基于事件的作业,必须用CREATE_JOB过程设置以下两个附加属性:
? queue_spec:对队列的指定,包括应用程序将消息放入其中以引发作业启动事件的队列的名称;对于安全队列,则为,对。
? event_condition:基于消息属性的条件表达式,此表达式的求值结果必须为TRUE,消息才能启动作业。只要消息有效负载是用户定义的对象类型,并且将表达式中的对象属性加了tab.user_data前缀,就可以在表达式中包含用户数据属性。
可以将queue_spec和event_condition指定为内嵌作业属性,或者用这两个属性创建基于事件的调度,然后创建一个引用此调度的作业。
8、使用EM创建基于事件的调度
使用“Create Schedule(创建调度)”页,可以从标准的、基于时间的调度和基于事件的调度之中选择一种来创建。如果选择基于事件的调度,可以指定队列名、代理名和事件条件以及其它调度属性。
注意:对于与event_condition匹配的事件的每次发生,调度程序都会运行基于事件的作业。但是,在作业运行过程中发生的事件将会被忽略;事件继续运行,但不会触发作业再次运行。
例子:使用EM创建基于事件的调度
创建队列代理并关联队列
SQL> EXEC DBMS_AQADM.CREATE_AQ_AGENT(agent_name => 'ADMIN_AGNT1');
PL/SQL procedure successfully completed.
SQL> DECLARE
subscriber SYS.AQ$_AGENT;
BEGIN
subscriber := SYS.AQ$_AGENT('ADMIN_AGNT1', NULL, NULL);
DBMS_AQADM.ADD_SUBSCRIBER(
queue_name => 'ALERT_QUE',
subscriber => subscriber,
rule => NULL,
transformation => NULL);
END;
/
PL/SQL procedure successfully completed.
SQL>BEGIN
DBMS_AQADM.ENABLE_DB_ACCESS(
agent_name => 'ADMIN_AGNT1',
db_username => 'SYS');
END;
/
PL/SQL procedure successfully completed.
DECLARE
subscriber SYS.AQ$_AGENT;
BEGIN
subscriber := SYS.AQ$_AGENT('ADMIN_AGNT1', NULL, NULL);
DBMS_AQADM.ADD_SUBSCRIBER(
queue_name => 'SCHEDULER_FILEWATCHER_Q',
subscriber => subscriber,
rule => NULL,
transformation => NULL);
END;
/
BEGIN
sys.dbms_scheduler.create_event_schedule(
event_condition=>'''tab.user_data.event_type="DISK_FULL"''',
queue_spec=>'"SYS"."ALERT_QUE","ADMIN_AGNT1"',
start_date=>to_timestamp_tz('2016-08-21 22:20:00 Asia/Shanghai','YYYY-MM-DD HH24:MI:SS TZR'),
schedule_name=>'"SYS"."SCH1"');
END;
10、创建一个基于事件的作业
示例:创建一个作业,如果成批装入的数据文件在上午9:00 前到达文件系统,则运行此作业。
BEGIN
DBMS_SCHEDULER.CREATE_JOB(
job_name=>'ADMIN.PERFORM_DATA_LOAD',
job_type => 'EXECUTABLE',
job_action => '/loaddir/start_my_load.sh',
start_date => SYSTIMESTAMP,
event_condition => 'tab.user_data.object_owner =
''HR'' and tab.user_data.object_name = ''DATA.TXT''
and tab.user_data.event_type = ''FILE_ARRIVAL''
and tab.user_data.event_timestamp < 9 ',
queue_spec => 'HR.LOAD_JOB_EVENT_Q');
END;
创建基于事件的作业
要将事件信息指定为作业属性,请使用CREATE_JOB的替代语法,其中包括queue_spec和event_condition属性。作业可以包含作为作业属性的内嵌事件信息,或可以通过指向事件调度来指定事件信息。显示的示例使用了内嵌的基于事件的调度。
示例显示了一个作业,只要有文件在上午9:00 前到达操作系统,就会启动此作业。假定消息有效负载是一个对象,该对象包括四个名称分别为object_owner、object_name、event_type和event_timestamp的属性。
本例使用的是用户定义的事件。因此,文件在到达了文件系统后,必须有一个程序或过程将包含正确信息的事件对象类型入队到指定的事件队列中,才能启动此作业。HR.LOAD_JOB_EVENT_Q队列必须与用于将事件的发生通知调度程序的事件对象类型相同。即,HR.LOAD_JOB_EVENT_Q队列必须是类型化队列,其中的类型包括四个名称为object_owner、object_name、event_type和event_timestamp的属性。
11、基于事件的调度
事件类型:
? 用户或应用程序生成的事件
? 调度程序生成的事件
由调度程序作业引发的事件:
? JOB_STARTED
? JOB_SUCCEEDED
? JOB_FAILED
? JOB_BROKEN
? JOB_COMPLETED
? JOB_STOPPED
? JOB_SCH_LIM_REACHED
? JOB_DISABLED
? JOB_CHAIN_STALLED
? JOB_ALL_EVENTS
? JOB_RUN_COMPLETED
? JOB_OVER_MAX_DUR
引发事件的示例:
DBMS_SCHEDULER.SET_ATTRIBUTE('hr.do_backup', 'raise_events', DBMS_SCHEDULER.JOB_FAILED);
基于事件的调度
你可以创建一个作业,但不向作业分配调度,而是通过直接引用事件来启动作业。有两种类型的事件:
? 用户或应用程序生成的事件:应用程序可以引发由调度程序使用的事件。调度程序通过启动作业来回应事件。此类事件的示例:正在运行的作业完成;文件到达文件系统;数据库内的帐户被锁定;以及库存到达下限阈值。
? 调度程序生成的事件:调度程序可以引发事件来指示在调度程序自身内发生的状态更改。例如,在作业启动时、作业完成时、作业超出其分配的运行时间时等条件下,调度程序均可以引发事件。事件的使用者是一个应用程序,该应用程序将执行某个操作以便对事件做出响应。
你可以对作业进行配置,使调度程序在作业的状态更改时引发事件。可以通过设置raise_events作业属性来完成此操作。默认情况下,在变更作业的raise_events属性之前,作业不会引发任何状态更改事件。要变更此属性,必须先使用CREATE_JOB过程创建作业,然后使用SET_ATTRIBUTE过程修改此属性的默认值。本例显示hr.do_backup作业已变更,使得该作业在失败时引发事件。
为作业启用作业状态更改事件后,调度程序将引发这些事件,方法是将消息入队到默认的事件队列SYS.SCHEDULER$_EVENT_QUEUE中。
默认的调度程序事件队列是一个安全队列。可能需要配置队列,才能允许特定用户在此队列上执行操作,具体取决于应用程序。查看文档Oracle Streams Concepts and Administrator获取关于安全队列的详细信息。
默认的调度程序事件队列主要用于调度程序生成的事件。Oracle 建议你不要将此队列用于用户应用程序或用户定义的事件。
12、创建复杂调度
创建复杂调度
调度是数据库中的对象。创建调度时,将自动保存这些调度。可以使用调度的组合来创建更复杂的调度。通过组合调度,可以向日历表达式中添加特定日期,或者从日历表达式中排除特定日期。
定义调度的重复间隔时可以使用下列选项:
? INCLUDE:向日历表达式结果中添加日期列表
? EXCLUDE:从日历表达式结果中删除日期列表
? INTERSECT:只使用两个或多个调度共有的日期
创建要组合使用的调度时,可以对日期列表进行编码,方法是包含形式为[YYYY]MMDD的硬编码日期,或者包含用CREATE_SCHEDULE过程创建的已命名调度。例如,可以对调度的重复间隔使用下列值来指定日期列表:
0115,0315,0325,0615,quarter_end_dates,1215
此字符串表示日期1 月15 日、3 月15 日、3 月25 日、6 月15 日、12 月15 日以及QUARTER_END_DATES调度所指定的日期列表。
如果未在调度中指定硬编码日期的可选年份,则包括每一年的这些日期。
13、使用邮件通知
? 作业状态更改的电子邮件通知
? 是由作业状态事件触发的
? 多个通知,多个收件人
? *_SCHEDULER_NOTIFICATIONS视图
使用调度程序电子邮件通知:
1. 指定你将用来发送电子邮件的SMTP 服务器的地址:
DBMS_SCHEDULER.SET_SCHEDULER_ATTRIBUTE
('email_server','host[:port]');
2. 还可以设置默认的发件人电子邮件地址:
DBMS_SCHEDULER.SET_SCHEDULER_ATTRIBUTE
('email_sender','valid email address');
3. 为指定作业添加电子邮件通知。
使用作业电子邮件通知功能,可以向现有作业添加电子邮件通知,以便作业中发生受关注事件后向指定的电子邮件地址发送通知。对于每个作业,可以为不同的事件添加通知。可以将电子邮件通知发送给多个收件人。
要启用电子邮件通知功能,你必须:
1.设置email_server调度程序属性。
2.此外,还可以使用email_sender调度程序属性指定电子邮件通知的默认发件人电子邮件地址。
3.创建作业后,执行DBMS_SCHEDULER.ADD_JOB_EMAIL_NOTIFICATION过程,为作业添加一个或多个通知。
数据字典通过*_SCHEDULER_NOTIFICATIONS视图支持电子邮件通知。
14、增加和删除邮件通知
DBMS_SCHEDULER.ADD_JOB_EMAIL_NOTIFICATION (
job_name IN VARCHAR2,
recipients IN VARCHAR2,---逗号分隔的电子邮件地址列表
sender IN VARCHAR2 DEFAULT NULL,
subject IN VARCHAR2
DEFAULT dbms_scheduler.default_notification_subject,
body IN VARCHAR2
DEFAULT dbms_scheduler.default_notification_body,
events IN VARCHAR2---必需的逗号分隔列表
DEFAULT 'JOB_FAILED,JOB_BROKEN,JOB_SCH_LIM_REACHED,
JOB_CHAIN_STALLED,JOB_OVER_MAX_DUR',
filter_condition IN VARCHAR2 DEFAULT NULL);
DBMS_SCHEDULER.REMOVE_JOB_EMAIL_NOTIFICATION (
job_name IN VARCHAR2,
recipients IN VARCHAR2 DEFAULT NULL,
events IN VARCHAR2 DEFAULT NULL);
添加和删除电子邮件通知
使用DBMS_SCHEDULER.ADD_JOB_EMAIL_NOTIFICATION过程添加一个或多个作业电子邮件通知。只要作业生成了指定的任一事件,系统就会将电子邮件发送至指定的收件人地址。如果指定了过滤条件,则只有与FILTER_CONDITION中的条件匹配的事件将生成电子邮件。
如果未设置EMAIL_SERVER调度程序属性或指定的作业不存在,则该过程将失败。调用该过程的用户必须是作业的所有者,必须具有CREATE ANY JOB系统权限,或已经被授予了对作业的ALTER权限。
? 通知电子邮件的主题可以包含值将被替换的下列变量:%job_owner%、%job_name%、%event_type%、%event_timestamp%、%log_id%、
%error_code%、%error_message%、%run_count%、%failure_count%、%retry_count%、%job_subname%、%job_class_name%。
? 通知电子邮件的正文可以包含主题中有效的任何变量。
? 逗号分隔的事件列表不能为空。
? 如果filter_condition为空(默认值),则只要指定事件出现,系统就会向所有指定的收件人地址发送通知。
使用DBMS_SCHEDULER.REMOVE_JOB_EMAIL_NOTIFICATION过程删除指定作业的一个或多个电子邮件通知。
15、创建作业链
1. 创建链对象。
2. 定义链步骤。
3. 定义链规则。
4. 启动链:
– 启用链。
– 创建指向链的作业。
链是为达到组合目标而链接在一起的一系列已命名的程序。这被称为“依存调度”。链的示例如下所示:
运行程序A然后运行程序B,如果程序A和程序B成功完成,则只运行程序C,否则运行程序D。
在相互依赖的程序构成的链中,每个位置都称为一个步骤。通常情况下,链的一系列初始步骤启动之后,后续步骤的执行依赖于一个或多个之前步骤的完成。要创建并使用链,请依次完成下列步骤。如果没有特别说明,则提到的所有过程都是DBMS_SCHEDULER程序包的一部分。
1.使用CREATE_CHAIN过程创建链。可以选择用模式名称来限定链名称(如myschema.myname)。
2.定义(一个或多个)链步骤。定义步骤时需要命名此步骤并指定步骤中发生的操作。
每个步骤都可以指向下列项之一:
- 程序
- 另一个链(嵌套链)
- 事件
通过调用DEFINE_CHAIN_STEP过程来定义指向程序或嵌套链的步骤。
要定义等待事件发生的步骤,请使用DEFINE_CHAIN_EVENT_STEP过程。过程参数可以指向事件调度,也可以包括内嵌队列说明和事件条件。指向事件的步骤会一直等待,直到指定事件被引发。如果此事件发生,则步骤将成功完成。
3.创建链对象后,请定义链规则。链规则定义各步骤运行的时间,并定义各步骤之间的依赖关系。每个规则都有一个“条件”和一个“操作”:
- 如果条件的求值结果为TRUE,则执行操作。条件中可以包含在SQL WHERE子句中有效的任何语法。条件通常基于前面一个或多个步骤的结果。例如,你可能希望使某步骤在前两个步骤全部成功的情况下运行,而使另一个步骤在前两个步骤未全部成功的条件下运行。
- 操作指定在触发规则时要执行的内容。
典型操作是运行指定的步骤。可能的操作包括启动或停止步骤。还可以选择结束作业链的执行、返回一个值或者返回步骤名和错误代码。
添加到链中的所有规则共同确定了链的整体行为。在作业启动时以及在每个步骤结束时,将对所有规则求值以确定接下来发生的操作。请使用DEFINE_CHAIN_RULE过程向链中添加规则。向链中添加每个规则时均需要调用一次此过程。
4.启动链包括两个操作:
- 用ENABLE过程启用链。(链总是以禁用状态创建,因此可以在任何作业执行该链之前向其中添加步骤和规则。)启用一个已启用的链不会返回错误。
- 要运行链,必须创建'CHAIN'类型的作业。作业操作必须引用链名称。可以为此作业使用基于事件的调度或基于时间的调度。
16、作业链示例
在此链示例中,请考虑在批量数据加载期间出现的所有任务和条件。首先,必须有要加载的数据。然后加载数据,同时观察文件系统以确保在加载期间不会发生空间不足的情况。在数据加载完成后,需要重建在更新后的表上定义的索引。然后针对新加载数据运行报表。该幻灯片显示了一个依存调度示例。
例子:作业链
BEGIN
sys.dbms_scheduler.create_chain(
chain_name=>'"SYS"."JOBCHAIN2"');
sys.dbms_scheduler.define_chain_step(
chain_name=>'"SYS"."JOBCHAIN2"',
step_name=>'"JOBC1"',
program_name=>'"SYS"."EMPROWNUM"');
sys.dbms_scheduler.alter_chain(
chain_name=>'"SYS"."JOBCHAIN2"',
step_name=>'"JOBC1"',
attribute=>'pause',
value=>FALSE);
sys.dbms_scheduler.alter_chain(
chain_name=>'"SYS"."JOBCHAIN2"',
step_name=>'"JOBC1"',
attribute=>'skip',
value=>FALSE);
sys.dbms_scheduler.define_chain_step(
chain_name=>'"SYS"."JOBCHAIN2"',
step_name=>'"JOBC2"',
program_name=>'"SYS"."AUTO_SQL_TUNING_PROG"');
sys.dbms_scheduler.alter_chain(
chain_name=>'"SYS"."JOBCHAIN2"',
step_name=>'"JOBC2"',
attribute=>'pause',
value=>FALSE);
sys.dbms_scheduler.alter_chain(
chain_name=>'"SYS"."JOBCHAIN2"',
step_name=>'"JOBC2"',
attribute=>'skip',
value=>FALSE);
sys.dbms_scheduler.define_chain_rule(
chain_name=>'"SYS"."JOBCHAIN2"',
condition=>'jobc1 SUCCEEDED',
action=>'START JOBC2');
END;
17、高级调度器概念
使用高级调度程序功能,可以对调度的各个方面施加更强的控制,例如作业窗口和区分作业优先级。
? “窗口”由定义好起始时间和结束时间的时间间隔表示,用于在不同时间激活不同的资源计划。这使你可以更改某一时段(如一天或销售年度内的某一时间段)的资源分配。
? “窗口组”表示一系列窗口,使用它可以更轻松地管理窗口。可以将窗口或窗口组用于作业的调度,以确保作业仅在窗口及其关联资源计划有效时运行。
? “作业类”定义了一类作业,这些作业具有共同的资源使用要求和其它特性。作业类将作业分组到更大的实体中。
? 与作业类关联的“资源使用者组”确定分配给作业类中作业的资源。
? 使用“资源计划”,用户可以在各资源使用者组中区分资源(特别是CPU)的优先级。
注:灰色对象不是调度程序对象。
18、作业类
? 为其中的成员作业分配一组相同的属性值
? 是由CREATE_JOB_CLASS过程创建的
? 在作业类中指定作业(使用SET_ATTRIBUTE过程)
? 属于SYS方案
? 为成员作业设置资源分配
? 将服务属性设置为所需的数据库服务名称
? 将作业分组以区分优先级
作业类
作业类是参与的成员作业的策略。每个作业类指定一组属性,例如日志记录级别。为作业类分配作业时,作业可以继承这些属性。例如,可以指定用相同的策略来清除所有工资单作业的日志项。
? 可以使用CREATE_JOB_CLASS过程来创建作业类。类始终属于sys模式。要创建类,必须具有MANAGE SCHEDULER权限。存在一个名为DEFAULT_JOB_CLASS的默认作业类,此作业类是随数据库一起创建的。
? 创建了作业类之后,可以在创建作业时或者在创建作业之后,将作业指定为此作业类的成员,方法是使用DBMS_SCHEDULER程序包中的SET_ATTRIBUTE过程。如果没有将作业与某个作业类相关联,则作业属于该默认作业类。
? 将作业类的服务属性设置为所需的数据库服务名称。这决定了Real Application Clusters环境中运行成员作业的实例和指定给成员作业的系统资源(后者可选)。
? 为成员作业设置资源分配。作业类可以将数据库资源管理器与调度程序关联起来,因为每个作业类可以将一个资源使用者组指定为一个属性。这样成员作业就属于指定的使用者组,并根据当前资源计划中的设置分配资源。此外,还可以将resource_consumer_group属性保留为NULL并将作业类的服务属性设置为所需的数据库服务名称。然后,该服务可以映射到资源使用者组。如果同时设了resource_consumer_group和服务属性并且指定的服务映射到资源使用者组,则优先使用resource_consumer_group属性中指定的资源使用者组。如果创建作业类时未指定资源使用者组,则作业类将映射到DEFAULT_CONSUMER_GROUP资源使用者组。启用了资源管理器后,对于默认作业类中的作业或者与默认资源使用者组关联的作业类中的作业,可能无法为其分配足够的资源以完成各自的任务。
? 为作业分组以区分优先级。可以为同一作业类中的单个作业分配1到5之间的优先级值,这样如果将该类中的两个作业安排在同一时间启动,优先级较高的作业优先。这样可以确保次要作业不会阻碍重要作业及时完成。如果为两个作业指定的优先级值相同,启动日期较早的作业优先。如果没有为作业指定优先级值,则其优先级默认为3。
例子:创建作业类
BEGIN
sys.dbms_scheduler.create_job_class(
logging_level=>DBMS_SCHEDULER.LOGGING_RUNS,
log_history=>10,
resource_consumer_group=>'SYS_GROUP',
service=>'stone',
job_class_name=>'"JOBCLASS1"');
END;
19、窗口
调度程序窗口:
? 可以在不同时间段启动作业或更改资源在作业间的分配
? 一次只有一个是活动的
? 是使用CREATE_WINDOW过程创建的
作业的优先级可以随时间而变化。例如,在夜间可能要将大部分数据库资源分配给数据仓库加载作业,而在白天则将较大一部分资源分配给应用程序作业。要实现这一点,可以使用调度程序窗口来更改数据库资源计划。
? 调度程序窗口可以在一天、一周等的不同时间段自动启动作业或更改资源在作业间的分配。窗口由定义好起始时间和结束时间的时间间隔表示,例如“从凌晨12:00到早上6:00”。
? 在任意给定时间内,只能存在一个有效的窗口。
? 可以使用CREATE_WINDOW过程创建窗口。
调度程序窗口使用作业类来控制资源分配。每个窗口指定了在该窗口打开(生效)时激活的资源计划,而每个作业类指定了资源使用者组或可以映射到使用者组的数据库服务。因此,在窗口中运行的作业所拥有的资源是根据其作业类的使用者组和窗口的资源计划分配的(如图形所示)
20、区分窗口内作业的优先级
区分作业优先级:
? 在类级别(通过资源计划)
? 在作业级别(使用作业优先级属性)
? 不能担保不同作业类中的作业的优先级
在一个数据库中创建多个作业时,你需要通过某种方式来根据你的业务需求安排作业处理,并指定哪些作业具有最高优先级。对于特定窗口,可能有多种类别的作业在运行,每一类都具有各自的优先级。
可以在两个级别区分优先级:类级别和作业级别。
? 首先是在类级别区分优先级,使用的是资源计划。完全根据类资源分配情况来区分属于不同类的作业的优先级。
? 其次是在类内区分优先级,使用的是作业的作业优先级属性。
仅当属于同一个类的两个作业同时启动时,才会考虑优先级。优先级较高的作业先启动。
不能确保不同作业类中的作业按其优先级运行。例如,APPL_JOBS作业类中的高优先级作业不一定在ADMIN_JOBS作业类中的低优先级作业之前启动,即使它们共享同一个计划时也是如此。如果APPL_JOBS作业类有较低级别的资源可用,则该类中的高优先级作业必须等待资源可用,即使在其它作业类中存在可用于低优先级作业的资源时也是如此。
21、创建一个作业数组
1. 声明sys.job和sys.job_array类型的变量:
DECLARE
newjob sys.job;
newjobarr sys.job_array;
2. 初始化作业数组:
BEGIN
newjobarr := SYS.JOB_ARRAY();
3. 调整作业数组大小以存储所需的作业数量:
newjobarr.EXTEND(100);
使用作业数组是一种效率较高的创建作业集的方式。这也适用于轻量作业。在示例中,在一个作业数组中创建了100 个作业说明,并且将这些说明提交到了单个事务处理的作业队列中。请注意,对于轻量作业,所需的信息量非常有限。在此示例中,start_time参数默认为NULL,因此作业被安排为立即启动。
1.声明用于存储作业定义的变量和一个作业数组变量。
2. 使用SYS.JOB_ARRAY构造器初始化作业数组。这将为数组中的每个作业创建一个位置。
3.将数组大小设置为预期的作业数。
4. 创建每个作业,并将其放入数组中。在示例中,唯一的差异是作业的名称。
作业的start_time变量将被省略并默认为NULL,表示将立即运行该作业。
5. 使用CREATE_JOBS过程将数组中的所有作业作为一个事务处理提交。
注:如果数组很小,则性能不会显著优于提交单个作业时的性能。
22、创建一个作业数组
4. 将作业放入作业数组:
FOR i IN 1..100 LOOP
newjob := SYS.JOB(job_name=> 'LWTJK'||to_char(i),
job_style => 'LIGHTWEIGHT',
job_template => 'MY_PROG',
enabled => TRUE );
newjobarr(i) := newjob;
END LOOP;
5. 将作业数组作为一个事务处理提交:
DBMS_SCHEDULER.CREATE_JOBS(newjobarr, 'TRANSACTIONAL');
此示例的全部代码如下:
DECLARE
newjob sys.job;
newjobarr sys.job_array;
BEGIN
-- Create an array of JOB object types
newjobarr := sys.job_array();
-- Allocate sufficient space in the array
newjobarr.extend(100);
-- Add definitions for jobs
FOR i IN 1..100 LOOP
-- Create a JOB object type
newjob := sys.job(job_name => 'LWTJK' || to_char(i),
job_style => 'LIGHTWEIGHT',
job_template => 'PROG_1',
enabled => TRUE );
-- Add job to the array
newjobarr(i) := newjob;
END LOOP;
-- Call CREATE_JOBS to create jobs in one transaction
DBMS_SCHEDULER.CREATE_JOBS(newjobarr, 'TRANSACTIONAL');
END;
/
22、创建文件监视器及基于事件的作业
请执行以下任务:
1. 创建调度程序身份证明对象并授予EXECUTE权限。
2. 创建文件监视器并授予EXECUTE权限。
3. 创建其元数据参数引用事件消息的调度程序程序对象。
4. 创建引用文件监视器的基于事件的作业。(可选,使作业能够针对文件到达事件的每个实例运行。)
5. 启用文件监视器、程序和作业。
创建文件监视器和基于事件的作业
执行以下任务来创建文件监视器和在指定文件到达后启动的基于事件的作业:
1.创建调度程序身份证明对象(身份证明),用于通过主机操作系统验证对文件的访问权限,并将对身份证明的EXECUTE权限授予文件监视器将启动的基于事件的作业所属的模式。
2.创建文件监视器并将对文件监视器的EXECUTE权限授予引用文件监视器的基于事件的作业所属的任何模式。
3.创建其元数据参数引用事件消息的调度程序程序对象。
- 使用EVENT_MESSAGE属性定义元数据参数。
- 创建具有一个类型为SYS.SCHEDULER_FILEWATCHER_RESULT的参数的存储过程,供该程序调用。存储过程必须具有一个SYS.SCHEDULER_FILEWATCHER_RESULT类型(事件消息的数据类型)的参数。该参数的位置必须与所定义的元数据参数的位置相匹配。该过程可以访问此抽象数据类型的属性,以了解有关已到达文件的信息。
4.创建引用文件监视器的基于事件的作业。可以使用DBMS_SCHEDULER.SET_ATTRIBUTE过程使作业能够针对文件到达事件的每个实例运行,即使该作业已经在处理之前的一个事件。将PARALLEL_INSTANCES属性设置为TRUE。
BEGIN
DBMS_SCHEDULER.SET_ATTRIBUTE('','PARALLEL_INSTANCES', TRUE);
END;
这样作业将作为轻量作业运行,因此可以快速启动作业的多个实例。如果将PARALLEL_INSTANCES设置为默认值FALSE,则当基于事件的作业已经在处理
一个事件时,此时出现的文件监视器事件将被丢弃。
5.启用文件监视器、程序和作业。
23、从远程系统启用文件到达事件
执行以下任务在远程系统上启用文件到达事件触发功能:
1. 设置数据库以运行远程外部作业。
2. 在第一个远程系统上安装、配置、注册并启动调度程序代理。
3. 为其余的每个远程系统重复步骤2。
要接收来自远程系统的文件到达事件,你必须在系统上安装调度程序代理,且必须在数据库中注册代理。该远程系统不需要Oracle数据库实例来生成文件到达事件。
24、调度远程数据库作业
? 创建一个作业在同一主机或远程主机上的另一个数据库实例上运行存储过程和匿名PL/SQL块。
? 目标数据库可以是任何版本的Oracle数据库。
? DBMS_SCHEDULER.CREATE_DATABASE_DESTINATION和DBMS_SCHEDULER.CREATE_CREDENTIAL可以用于远程数据库作业。
? 作业类型为PLSQL_BLOCK和STORED_PROCEDURE的作业可以是SET_ATTRIBUTE为DESTINATION和CREDENTIAL属性调用的目标。
现在,可以创建一个作业在同一主机或远程主机上的另一个数据库实例上运行存储过程和匿名PL/SQL块。目标数据库可以是任何版本的Oracle数据库。
现在没有用于支持远程数据库作业的新过程,但是对现有DBMS_SCHEDULER过程进行了更改以支持该功能。以下提供了详细信息。
25、创建远程数据库作业
执行以下任务以创建远程作业:
1. 设置远程作业的发起数据库。
2. 使用DBMS_SCHEDULER.CREATE_JOB创建作业。
3. 使用DBMS_SCHEDULER.CREATE_CREDENTIAL创建身份证明。
4. 使用DBMS_SCHEDULER.SET_ATTRIBUTE设置作业CREDENTIAL_NAME。
5. 使用DBMS_SCHEDULER.SET ATTRIBUTE设置作业的DESTINATION属性。
6. 使用DBMS_SCHEDULER.ENABLE启用作业。
创建远程数据库作业
你可以执行示例中列出的任务来创建远程数据库作业。
要设置远程作业的发起数据库,请执行以下步骤:
1. 验证是否已安装XML DB。
2. 启用至数据库的HTTP连接。
BEGIN
DBMS_XDB.SETHTTPPORT(port);
END;
3. 执行prvtrsch.plb脚本。
4. 设置调度程序代理的注册口令。
BEGIN
DBMS_SCHEDULER.SET_AGENT_REGISTRATION_PASS('password');
END;
26、调度多个目标作业
? 该功能可用于指定要在其上执行作业的多个目标。
? 它使用户能够从创建作业的数据库监视和控制这些作业。
? 多目标作业运行时,可将其视为一个作业集合,其中的各个作业是彼此近似相同的副本。
? 所有作业都将基于在作业开始日期中指定的时区运行,或将使用源数据库的时区。
多目标作业功能允许你指定多个要在其上执行作业的目标。你可以从创建作业的数据库监视和控制这些作业。其中包括以下功能:
? 指定必须在其上执行作业的数个数据库或计算机
? 将在多个目标上调度的同一作业作为单个实体进行修改
? 停止或删除在一个或多个远程目标上运行的作业
? 查看作业实例在所有作业目标上的状态
请注意,在该功能的初始版本中,所有目标都基于在作业开始日期中指定的时区运行,或默认使用源数据库的时区。
27、查看调度器元数据
主要调度程序管理视图,显示:
? *_SCHEDULER_JOBS:所有作业,包括启用的和禁用的
? *_SCHEDULER_SCHEDULES:所有调度
? *_SCHEDULER_PROGRAMS:所有程序
? *_SCHEDULER_RUNNING_JOBS:活动的作业状态
? *_SCHEDULER_JOB_LOG:所有作业状态更改
? *_SCHEDULER_JOB_RUN_DETAILS:所有已完成的作业运行
SELECT job_name, status, error#, run_duration
FROM USER_SCHEDULER_JOB_RUN_DETAILS;
JOB_NAME STATUS ERROR# RUN_DURATION
---------------- ------ ------ ------------
GATHER_STATS_JOB SUCCESS 0 +000 00:08:20
PART_EXCHANGE_JOB FAILURE 6576 +000 00:00:00
查看调度程序元数据
作业表是一个包含所有作业的容器,每个数据库有一个作业表。作业表用于存储所有作业的信息,例如所有者名称和日志记录级别。可以在*_SCHEDULER_JOBS视图中查看这些信息。
作业是数据库对象,因此可能会累积起来并占用过多的空间。为避免发生这种情况,默认情况下会在作业完成后自动删除作业对象。该行为由auto_drop作业属性控制。
DBA 和授权用户可以通过多个视图查看与调度程序、作业、调度和窗口等有关的重要操作信息。这些视图包括:
? *_SCHEDULER_PROGRAM_ARGS:显示为所有程序定义的所有参数及默认值(如果存在)
? *_SCHEDULER_JOBS:显示所有的作业,不管是已启用还是禁用的作业
? *_SCHEDULER_JOB_RUN_DETAILS显示所有已完成(失败或成功)的作业运行。
每个作业实例都有对应的一行。每一行中都包含有关该实例的作业执行情况的信息。
ERROR# 是遇到的第一个错误的编号
? *_SCHEDULER_GLOBAL_ATTRIBUTE:显示调度程序属性的当前值
? *_SCHEDULER_JOB_ARGS:显示所有作业的所有已设置的参数值
? *_SCHEDULER_JOB_CLASSES:显示所有作业类
对于作业链:
? *_SCHEDULER_RUNNING_CHAINS:显示所有活动链
? *_SCHEDULER_CHAIN_STEPS:显示所有链的所有步骤
? *_SCHEDULER_CHAINS:显示所有链
? *_SCHEDULER_CHAIN_RULES:显示所有链的所有规则
对于其它高级对象窗口:
? *_SCHEDULER_WINDOWS显示所有窗口
? *_SCHEDULER_WINDOW_GROUPS显示所有窗口组
? *_SCHEDULER_WINGROUP_MEMBERS显示所有窗口组的成员,一行对应一个组成员
? *_SCHEDULER_JOB_LOG显示作业的所有状态更改
? *_SCHEDULER_CREDENTIALS显示数据库中的身份证明的列表,其中密码进行了模糊处理
? *_SCHEDULER_JOB_ROLES按数据库角色显示所有作业
轻量作业与常规作业是通过相同的视图查看的:
? *_SCHEDULER_JOBS:显示所有作业,包括JOB_STYLE=‘LIGHTWEIGHT’的作业
? *_SCHEDULER_JOB_ARGS:还显示轻量作业的所有已设置的参数值
? 因为轻量作业不是数据库对象,所以无法通过*_OBJECTS视图查看轻量作业
从Oracle Database 11gR2 开始:
? *_SCHEDULER_NOTIFICATIONS显示已经设置的电子邮件通知
? *_SCHEDULER_FILE_WATCHERS显示文件监视器的配置信息
以下视图显示与多目标作业有关的信息:
? *_SCHEDULER_DESTS:显示可在其上调度远程作业的所有目标。该视图包含外部
目标(对于远程外部作业)和数据库目标(对于远程数据库作业)
? *_SCHEDULER_EXTERNAL_DESTS:显示所有已在数据库中注册且可用作远程外部作业目标的代理
? *_SCHEDULER_DB_DESTS:显示您可在其上调度远程数据库作业的所有数据库
? *_SCHEDULER_GROUPS:显示您的方案中的组或数据库中的所有组
? *_SCHEDULER_GROUP_MEMBERS:显示您的方案中的组成员或数据库中的所有组成员
? *_SCHEDULER_JOB_DESTS:显示远程数据库中的作业的状态
注:在上面列出的视图中,视图名称开头的星号会被替换为DBA、ALL或USER。
参考:http://blog.youkuaiyun.com/rlhua/article/details/13355531
来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/28536251/viewspace-2137270/,如需转载,请注明出处,否则将追究法律责任。
转载于:http://blog.itpub.net/28536251/viewspace-2137270/
本文详细介绍Oracle数据库调度程序的功能和使用方法,包括作业、程序、调度的创建与管理,以及基于时间与事件的调度策略。此外,还介绍了如何利用作业链、窗口、作业类等高级功能来提高调度效率。

![clipboard[1] clipboard[1]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_1492145369b04s.png)
![clipboard[2] clipboard[2]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_1492145377H8Co.png)
![clipboard[3] clipboard[3]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_1492145406cP5Y.png)
![clipboard[4] clipboard[4]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_1492145430x6X7.png)
![clipboard[5] clipboard[5]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_14921454452m2Q.png)
![clipboard[6] clipboard[6]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_1492145456NHA6.png)
![clipboard[7] clipboard[7]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_149214547147SC.png)
![clipboard[8] clipboard[8]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_1492145472T0jS.png)
![clipboard[9] clipboard[9]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_1492145473lU9l.png)
![clipboard[10] clipboard[10]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_1492145474kKbR.png)
![clipboard[11] clipboard[11]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_1492145475t2zn.png)
![clipboard[12] clipboard[12]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_1492145476Og5A.png)
![clipboard[13] clipboard[13]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_1492145477VasH.png)
![clipboard[14] clipboard[14]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_1492145478CMi4.png)
![clipboard[15] clipboard[15]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_1492145479OpKe.png)
![clipboard[16] clipboard[16]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_1492145480Ru9T.png)
![clipboard[17] clipboard[17]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_1492145480cg7c.png)
![clipboard[18] clipboard[18]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_1492145481Ljl0.png)
![clipboard[19] clipboard[19]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_149214548526V1.png)
![clipboard[20] clipboard[20]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_1492145486nZX3.png)
![clipboard[21] clipboard[21]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_1492145489V9S3.png)
![clipboard[22] clipboard[22]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_1492145490qm1R.png)
![clipboard[23] clipboard[23]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_1492145493MYK8.png)
![clipboard[24] clipboard[24]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_14921454945Xjw.png)
![clipboard[25] clipboard[25]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_1492145496R41L.png)
![clipboard[26] clipboard[26]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_1492145497JyXy.png)
![clipboard[27] clipboard[27]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_14921454984Q56.png)
![clipboard[28] clipboard[28]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_1492145501q46o.png)
![clipboard[29] clipboard[29]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_1492145505Z88N.png)
![clipboard[30] clipboard[30]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_1492145506myrR.png)
![clipboard[31] clipboard[31]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_1492145506wUh0.png)
![clipboard[32] clipboard[32]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_1492145507GyW5.png)
![clipboard[33] clipboard[33]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_1492145508FYTY.png)
![clipboard[34] clipboard[34]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_1492145509H13z.png)
![clipboard[35] clipboard[35]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_1492145509T0qe.png)
![clipboard[36] clipboard[36]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_1492145510Z3TF.png)
![clipboard[37] clipboard[37]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_1492145511p3IP.png)
![clipboard[38] clipboard[38]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_1492145511634A.png)
![clipboard[39] clipboard[39]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_1492145512kPh4.png)
![clipboard[40] clipboard[40]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_1492145512ll7z.png)
![clipboard[41] clipboard[41]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_1492145513Lh8q.png)
![clipboard[42] clipboard[42]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_1492145513BcGG.png)
![clipboard[43] clipboard[43]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_1492145514uMJ5.png)
![clipboard[44] clipboard[44]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_1492145515D3d3.png)
![clipboard[45] clipboard[45]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_14921455169x6Y.png)
![clipboard[46] clipboard[46]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_1492145517ThRk.png)
![clipboard[47] clipboard[47]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_1492145518n5nL.png)
![clipboard[48] clipboard[48]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_1492145518XutG.png)
![clipboard[49] clipboard[49]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_149214551909y7.png)
![clipboard[50] clipboard[50]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_1492145520mvOP.png)
![clipboard[51] clipboard[51]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_1492145521Mg2c.png)
![clipboard[52] clipboard[52]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_14921455224Ufi.png)
![clipboard[53] clipboard[53]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_1492145522rVfY.png)
![clipboard[54] clipboard[54]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_1492145523ZGIi.png)
![clipboard[55] clipboard[55]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_1492145523VRXJ.png)
![clipboard[56] clipboard[56]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_1492145524pPMf.png)
![clipboard[57] clipboard[57]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_1492145524y818.png)
![clipboard[58] clipboard[58]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_1492145525V8qv.png)
![clipboard[59] clipboard[59]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_1492145525r3vk.png)
![clipboard[60] clipboard[60]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_1492145526mINZ.png)
![clipboard[61] clipboard[61]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_14921455264r3q.png)
![clipboard[62] clipboard[62]](http://img.blog.itpub.net/blog/attachment/201704/14/28536251_1492145527uOf4.png)
1万+

被折叠的 条评论
为什么被折叠?



