Oracle Database 11g: 作者:Arup Nanda |
参见系列目录
最后,我要介绍您将在 Oracle Database 11g 中遇到并喜爱的很多其他特性。
每个新版本的 Oracle Database 中都会引入一组新进程的新缩写。下面是 Oracle Database 11g 中的新进程缩写列表:
进程 | 名称 | 描述 |
ACMS | 内存服务器原子控制文件 | 仅适用于 RAC 实例中。执行分布式 SGA 更新时,ACMS 可确保在所有实例上发生更新。如果某个实例上的更新失败,此进程也会在所有实例上回滚更新。可将 ACMS 视为两阶段提交协调器进程,用于 RAC 集群中的 SGA 更新。 |
DBRM | 数据库资源管理器 | 执行资源计划和其他与资源管理器相关的任务 |
DIA0 | 诊断进程 0 | 检测挂起情况和死锁。将来可能会引入多个进程,因此将名称设置为 diag0。将来如果引入其他进程,将分别命名为 dia1、dia2 等。 |
DIAG | 诊断进程 | 执行诊断、转储跟踪文件(如有必要)以及执行全局 oradebug 命令 |
FBDA | 闪回数据存档器 | Oracle Database 11g 提供新的“闪回存档”,用于写入对表所作的更改(请参阅本系列的“事务管理”部分)。此进程将写入闪回存档。 |
GTX0 | 全局事务进程 0 | 在 RAC 集群中,Oracle Database 现在提供改进的 XA 事务处理。此进程协调 XA 事务。如果 XA 上的数据库负载增长,将自动创建更多进程,分别命名为 GTX1、GTX2,直至 GTXJ。 |
KATE | ASM 临时任务处理器 | 毫无疑问,这是所有新进程中最长的名称。它是在 ASM 实例上出现的,而不是在数据库上出现。(有关新特性“快速磁盘重新同步”的信息,请参阅本系列中有关 ASM 的部分。)当磁盘脱机时,此进程将代替磁盘对 ASM 元文件执行代理 I/O。 |
MARK | 为重新同步协调器标记分配单元 | 有关 ASM 磁盘组弹性的更多详细信息,请参阅 ASM 部分。当磁盘出现故障时将会脱机,从而导致写入内容丢失。在这种情况下,此进程会将 ASM 分配单元 (AU) 标记为陈旧。当磁盘联机时,陈旧的段将被刷新。 |
SMCO | 空间管理器 | SMCO 是主空间管理进程,可动态分配和回收空间。它将生成从进程 Wnnn 以执行这些任务。 |
VKTM | 虚拟计时器进程 | 提供时钟时间(每秒钟更新一次)。以提升的优先级运行时,此进程每 20 毫秒更新一次。 |
W000 | 空间管理工作器进程 | 执行从 SMCO 收到的指令。根据需要生成更多进程,命名为 W000、W001 等。 |
此新特性不会将 SQL 语句涂上颜色,而是类似于将其标记为“重要”。
假设您正在对某个性能问题进行故障诊断,您怀疑该问题是由某条 SQL 语句导致的。您希望在每个自动负载信息库 (AWR) 快照中捕获该 SQL 语句。但 AWR 快照并不捕获所有 SQL 语句,而只捕获前几条语句。如何强制快照捕获特定的 SQL,而不管它是否包括在前几条 SQL 语句?
程序包 dbms_workload_repository 中的 add_colored_sql() 过程将 SQL 标记为“着色的”,或者将其标记为重要,应该在每个 AWR 快照中捕获该 SQL,而不管它是否包括在前几条 SQL 语句中。首先确定 SQL 语句,然后获取其 SQL_ID。要对该语句着色,请使用以下命令:
begin dbms_workload_repository.add_colored_sql( sql_id => 'ff15115dvgukr' ); end;
要找出已着色的 SQL,可以查询 AWR 表 WRM$_COLORED_SQL:
SQL> SELECT * FROM wrm$_colored_sql; DBID SQL_ID OWNER CREATE_TI ---------- ------------- ---------- -------- 2965581158 ff15115dvgukr 1 05-APR-08
之后,将在每个快照中捕获 ID 为 ff15115dvgukr 的 SQL 语句,即使该语句不包括在前几条 SQL 语句中。(当然,该 SQL 必须位于库缓存中才能在 AWR 快照中捕获它。)
但是,如果 SQL 不再需要着色(即,该语句不再重要,没有必要捕获它)时怎么办呢?可以通过反向执行该过程来取消着色。
begin dbms_workload_repository.remove_colored_sql( sql_id => 'ff15115dvgukr' ); end;
当您要在调优活动中关注某个 SQL 时,此特性极为有用。
与传言相反,COPY 命令仍然存在。并且它仍然是复制 LONG 数据类型的唯一方式。但由于此命令已被弃用,因此它已经跟不上 Oracle Database 的发展步伐。
例如,它不知道存在新的数据类型 BFILE。如果您尝试复制包含 BFILE 类型列(如先前的示例中所示)的 DOCS 表:
SQL> copy to sh@oradba11 - > replace docs_may08 - > using select * from docs > where creation_dt between '1-may-08' and '31-may-08'; Enter TO password: Array fetch/bind size is 15. (arraysize is 15) Will commit when done. (copycommit is 0) Maximum long size is 80. (long is 80) CPY-0012: Datatype cannot be copied
错误消息描述的是:COPY 命令无法复制 BFILE 数据类型。但如果表中包含 Long 类型列,则无法使用 INSERT /*+ APPEND */ 和 CREATE TABLE ...NOLOGGING AS SELECT ... 语句。在以下示例中,我们尝试将一个表中的某些行复制到另一个表中,第二个表中有一个定义为 LONG 类型的列:
SQL>create table mytab1 as select * from mytab where col1 = 'A'; create table mytab1 as select * from mytab * ERROR at line 1: ORA-00997: illegal use of LONG datatype SQL> insert /*+ APPEND */ into mytab 2> select * from mytab where col1 = 'A'; select * from mytab * ERROR at line 2: ORA-00997: illegal use of LONG datatype
因此,要复制大量数据而不填充撤销段,唯一简单的方法就是使用 COPY 命令。
在 Oracle Database 10g 中引入 Data Pump 时,预计最终将废弃传统的 Export 和 Import 工具。那么,请继续等待:在 Oracle Database 11g 中,这些旧工具仍然存在。由于已被弃用,这些工具不会再得到任何增强,但绝不能说它们是无用的。
就功能而言,Data Pump 胜过常规 Export/Import 许多倍,但在一种简单情况下,后者仍然非常有用:当您必须在客户端而不是服务器上创建转储文件时。由于执行操作前不必创建目录,因此在许多方面,通过常规 Export/Import 执行简单的数据导出可能更容易。
但是,需要再次声明的是,由于这些工具已被弃用,因此在使用时存在以下三个风险:
在分区表中,每个分区都可以有优化器统计信息。此外,在整个表上还有与分区无关的全局统计信息,例如,整个表中不同值的数量。仅在 dbms_stats.gather_*_stats 程序发出指令时才会收集此全局统计信息,默认情况下不会收集。如果未指定全局统计信息参数,则不会收集全局统计信息,优化器将根据分区统计信息生成全局统计信息。当然,计算得到的全局统计信息始终要比生成的统计信息精确,因此始终建议您收集全局统计信息。
但是,过去一直存在一个小问题:当分区数据变化但其他分区保持不变时,全局统计信息可能会变得陈旧。因此,即使只有一个分区发生变化,统计信息收集程序也必须遍历整个表来收集全局统计信息。
现在这个问题已不复存在。在 Oracle Database 11g 中,可以指示优化器仅从更改的分区中以增量方式收集全局统计信息,而不必再次执行整表扫描。通过将用于统计信息收集的表参数 INCREMENTAL 设置为 TRUE 可完成此操作,如下所示:
begin dbms_stats.set_table_prefs( 'SH','SALES','INCREMENTAL','TRUE'); end; /
现在,您应该专门收集此表中粒度为 AUTO 的分区 SALES_1995 上的统计信息。
begin dbms_stats.gather_table_stats ( ownname => 'SH', tabname => 'SALES', partname => 'SALES_1995', granularity => 'AUTO' ); end;
正如我先前提到的,自上一个版本开始,Data Pump 工具一直用于移动大量数据,或者对数据进行有效的“逻辑”备份。与 Export/Import 类似,它是独立于平台的工具(例如,可以从 Linux 导出,然后再导入 Solaris)。在 Oracle Database 11g 中,Data Pump 获得了一些增强。
Data Pump 存在的较大的一个问题是,创建转储文件时无法对其进行压缩。在较老的 Export/Import 实用程序中,这一点比较容易做到。在 Oracle Database 11g 中,Data Pump 可以在创建转储文件时对其进行压缩。这是通过在 expdp 命令行中使用参数 COMPRESSION 实现的。此参数有三个选项:
$ expdp global/global directory=dump_dir tables=units_fact dumpfile=units_fact_comp.dmp compression=all
$ expdp global/global directory=dump_dir tables=units_fact dumpfile=units_fact_uncomp.dmp
$ ls -l -rw-r----- 1 oracle dba 2576384 Jul 6 22:39 units_fact_comp.dmp -rw-r----- 1 oracle dba 15728640 Jul 6 22:36 units_fact_uncomp.dmp
如果使用 gzip 压缩转储文件:
$ gzip units_factl_uncomp.dmp -rw-r----- 1 oracle dba 3337043 Jul 6 22:36 units_fact_uncomp.dmp.gz
Data Pump 中的其他两项增强功能如下:
可以在安全性部分中了解这两个特性。
如果您是一位经验丰富的导出/导入(原先的 Export,即“exp”工具)用户,可能已使用正确(并且通常仔细进行了优化)的参数细致地准备了 exp/imp 脚本。但是,现在不再支持原先的导出!因此,您要转向 Data Pump;但是您所钟爱并为之付出心血的脚本该怎么办?在您的脚本中,您可能希望用 expdp 代替 exp;但是这并不像替换可执行文件那么简单。所有参数该怎么办?
例如,请考虑原有的 exp 参数“owner”,它执行特定用户的所有对象的导出。expdp 对应的参数不是 owner;而是“schema”。因此,不仅要将“exp”替换为“expdp”,而且还要替换所有参数 — 这项任务不仅耗时而且很容易出错。
可怜可怜我吧。在第 2 版中,有一种特殊模式的 Data Pump,可处理原来的“exp”实用程序参数。我们来考虑以上问题。原来的脚本如下:
$ exp \"/ as sysdba\" rows=n owner=sh
不必将参数“owner”替换为“schema”,只需在 expdp 中运行以下脚本:
$ expdp \"/ as sysdba\" rows=n owner=sh
输出如下:
Export: Release 11.2.0.1.0 - Production on Sat Sep 26 19:02:25 2009 Copyright (c) 1982, 2009, Oracle and/or its affiliates.All rights reserved.Connected to: Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - Production With the Partitioning, Automatic Storage Management, OLAP, Data Mining and Real Application Testing options Legacy Mode Active due to the following parameters:
Legacy Mode Parameter: "owner=sh" Location: Command Line, Replaced with: "schemas=sh"
Legacy Mode Parameter: "rows=FALSE" Location: Command Line, Replaced with: "content=metadata_only"
Legacy Mode has set reuse_dumpfiles=true parameter.
Legacy Mode has set nologfile=true parameter.
Starting "SYS"."SYS_EXPORT_SCHEMA_01": "/******** AS SYSDBA" content=metadata_only schemas=sh reuse_dumpfiles=true nologfile=true Processing object type SCHEMA_EXPORT/USER
… the rest of the output comes here …
注意该实用程序是如何自动检测与原有 exp 中相关的参数。该实用程序会轻松替换与 expdp 命令相关的参数(而不是投诉和终止执行并报告错误),如输出中的粗体部分所示。您的脚本不必更改。
现在,您知道该工具如何将“owner”自动替换为“schema”,可能有兴趣了解其他参数将受到什么影响。将忽略某些参数,例如“buffers”。原先的导出使用该参数,因为行是使用传统方式导出的。Data Pump 使用直接路径,因此,该参数现在无关紧要,因此被忽略。
大部分参数会自动转换为其 Data Pump 的对应参数。您之前看到了一个示例;另一个示例是原先 exp 中的“constraints=n”会在 Data Pump 中转换为“exclude=constraints”。该转换也很智能:会将 Data Pump 功能考虑在内。
请考虑原先导出中的“consistent=y”参数,该参数可导出截至本导出首次启动时存在的所有行。Data Pump 中的相应功能由参数“flashback”提供。但是,该参数不再取值“y”或“n”;而是需要一个时间戳。在转换期间,Data Pump 会记录下时间并针对 flashback 参数输入值。
其他在 Data Pump 中没有等效功能的参数将停止运行并报错。例如,原先导出中的“volsize”参数用于指定磁带设备的卷大小。由于 Data Pump 不支持磁带设备,该参数的存在将导致作业停止并报错。(要确定每个旧模式参数如何影响 expdp 和 impdp,请参阅实用程序指南 的第 4 章。)
谈到原先的 Export 实用程序时,您应注意一条告诫。是否还记得可以创建无段表?下面来温习一下:对于此类表,段是在插入第一行时即时创建的。在此之前,表没有任何分段。如果原先的导出没有发现分段,那么在导出期间会忽略这些表。如果只想单独导出元数据,请注意,转储文件将不会引用这些表。
当然,与时俱进的 Data Pump 没有此限制。导出时,它不会忽略无段表。
您知道,Data Pump 使用若干“从属进程”来并行执行工作。如果您有一个 RAC 数据库,有些时候,对所有从属进程使用数据库的所有实例(而非对所有从属进程只使用一个实例)可能更有帮助。命名相当恰当的新参数“cluster”可实现这一操作。该参数指定为
cluster=y
该参数的默认值为“y”。如果您要禁用该参数,应指定“n”。该参数可在 Data Pump Export 和 Import 中使用。
我们来考虑一种特殊情况。如果您有两个以上的实例,同时您希望以集群模式运行 Data Pump 从属进程,您希望该操作仅限于少数几个实例;而非所有实例,该怎么办呢?
这非常简单。您只需使用另一个参数 service_name。创建一个仅在要操作的实例上定义的服务名称,并在 Data Pump 中使用该服务名称。
以下是一个示例,其中有四个实例,您只希望针对实例 2 和 3 进行 Data Pump 作业。创建服务名称 nodes_2_3,如下所示:
$ srvctl add service -d <DatabaseName> -s nodes_2_3 -a RAC2,RAC3 -r RAC1,RAC4 -P BASIC
然后,您应在 Data Pump 中使用该参数:
cluster=y service_name=nodes_2_3
现在,Data Pump 作业将仅使用 RAC2 和 RAC3 实例,即节点 2 和 3。
要警惕一点:如果在节点 1 或 4 上启动 expdp 进程,那么 Data Pump 进程除了在节点 2 和 3 上启动外,也会在节点 1 或 4 上启动。与往常一样,在 RAC 数据库中以并行方式启动该 Data Pump 作业时,请确保在共享存储上创建转储文件,因为多个节点上的从属进程必须能够访问这些转储文件。
默认情况下,Data Pump 作业使用直接路径插入(即 /*+ APPEND */ hint)以直接路径模式加载数据。虽然这样能够提高进程的加载速度,但它加载到表的高水位标记以上并锁定表,直至加载完成。此外,由于它始终加载新的块,因此,当转储文件仅包含少数几行时,这会浪费空间。
如果您要加载一个表,而该表还受 DML 活动的影响,该怎么办?您可通过选择一个新参数 data_options 来执行此操作,该参数可更改默认行为。以下是一个示例:
$ impdp dumpfile=sales.dmp tables=sales directory=input_dir data_options=disable_append_hint
disable_append_hint 值强制 Data Pump 作业通过正常插入(而不是直接路径插入)来加载。现在,DML 可在正在加载的表中运行。
该参数的另一个值是 skip_constraint_errors,即使非延迟约束上有约束违例,它也允许加载继续。该属性有助于加载全部表,然后通过 novalidate 子句一次性启用约束。该操作使其在数据仓库中非常有用。
是否记得在重建索引时使用的 ONLINE 子句?
alter index in_tab_01 rebuild online;
在 Oracle Database 11g 中,联机重建真正处于联机状态:它不进行独占锁定。DML 不会受影响。
创建全局临时表时,所占用的空间是从哪里分配的呢?它来自用户的临时表空间。通常这不会成为问题,但在某些特殊情况下,您可能要出于预期目的释放临时表空间(通常是为了排序)。有时,您可能要创建临时表,以使用位于更快、更高效磁盘上的其他临时表空间来提高数据访问速度。在这些情况下,只能将该表空间变成用户的临时表空间。
在 Oracle Database 11g 中,可以使用另一个临时表空间来存放全局临时表。让我们看一看这种方案的实现方式。首先,再创建一个临时表空间:
SQL> create temporary tablespace etl_temp 2> tempfile '+DG1/etl_temp_01.dbf' 3> size 1G; Tablespace created.
SQL> create global temporary table data_load ( 2> input_line varchar2 (2000) 3> ) 4> on commit preserve rows 5> tablespace etl_temp; Table created.
假定您有一个名为 myscript.sql 的 SQL 脚本:
set puase on set lines 132 pages 0 trimsppol on select * from nonexistent_table /
Oracle Database 11g 提供了一个完美的解决方案:现在,您可以在一个专用表中记录来自 SQL*Plus 的错误。您应执行的第一条命令如下:
SQL> set errorlogging on
SQL> @myscript
SP2-0158: unknown SET option "puase" SP2-0158: unknown SET option "trimsppol" select * from nonexistent_table * ERROR at line 1: ORA-00942: table or view does not exist
sql> col timestamp format a15 sql> col username format a15 sql> col script format a10 sql> col identifier format a15 sql> col statement format a20 sql> col message format a20 sql> select timestamp, username, script, statement, message 2> from sperrorlog;
SPERRORLOG 表是专门针对此用途创建的。您还可以创建自己的表,并用 SQL*Plus 中的错误填充它。此表应按以下方式创建:
SQL> create table my_sperror_log 2 ( 3 username varchar2(256), 4 timestamp timestamp, 5 script varchar2(1024), 6 identifier varchar(256), 7 message clob, 8 statement clob 9 ) 10 / Table created.
SQL> set errorlogging on table sh.my_sperror_log; SQL> @myscript
SQL> set errorlogging on truncate
SQL> set errorlogging on identifier MYSESSION1
select timestamp, username, script, statement, message from sperrorlog where identifier = 'MYSESSION1';
您可能已经知道,临时表空间很特殊,普通的空间管理规则可能并不适用。分配了临时段之后,将不会再回收它。这不是一个真正的问题,因为临时段(使用临时表空间)不是模式的一部分,也不会在数据库回收空间中存储。其他用户或查询可重用此空间。不管怎样,由于此空间不再回收,因此临时表空间只能不断增大。但是,我们能否对这些空间进行修剪,从而为其他表空间腾出一些空间呢?
到目前为止,唯一的办法就是删除并重新创建表空间 — 这只是小事一桩,几乎可以在保持联机的状态下完成。不过也存在一点“小问题”:如果要求正常运行时间必须达到百分之百时怎么办?在 Oracle Database 11g 中,通过缩小临时表空间可以很容易实现这一点。
下面介绍如何缩减表空间 TEMP1:
alter tablespace temp1 shrink space;
SQL> select * from dba_temp_free_space; TABLESPACE_NAME TABLESPACE_SIZE ALLOCATED_SPACE FREE_SPACE ------------------------------ --------------- --------------- ---------- TEMP 179306496 179306496 178257920
SQL> alter tablespace temp shrink space keep 100m; Tablespace altered. SQL> select * from dba_temp_free_space; TABLESPACE_NAME TABLESPACE_SIZE ALLOCATED_SPACE FREE_SPACE ------------------------------ --------------- --------------- ---------- TEMP 105906176 1048576 104857600
Oracle 数据库中的 BFILE 数据类型存储指向外部文件的指针,而不存储文件本身的内容。它具有以下特定用途:当用户可以访问文件中的数据时可以节省数据库空间,这对于某些应用程序(如使用内容和多媒体丰富的数据库的应用程序)来说非常有用。
如果表中有一个 BFILE 类型列,您要在 SQL*Plus 中选择表的某些行,这在过去常常会发生错误。在 Oracle Database 11g 中,SQL*Plus 将文件位置作为列值显示。
让我们来看一个示例。首先,必须创建一个用于存储 BFILE 的目录。
SQL> create directory doc_dir as '/u01/oracle/docs'; Directory created.
SQL> create table docs 2 ( 3 doc_id number(20), 4 doc_file bfile, 5 doc_type varchar2(10) 6 ); Table created.
SQL> insert into docs values 2 ( 3 1, 4 bfilename('DOC_DIR','metric_daily_report.pdf'), 5 'PDF' 6 ); 1 row created.
SQL> col doc_file format a50 SQL> select * from docs; DOC_ID DOC_FILE DOC_TYPE ---------- -------------------------------------------------- ---------- 1 bfilename('DOC_DIR', 'metric_daily_report.pdf') PDF
考虑如下情形:在诊断某个数据库问题期间,您修改了很多参数,这些参数都位于内存中。后来,您忘记更改了哪些参数。除非您记得将这些参数放入初始化参数文件(pfile 或 spfile)中,否则这些更改将会丢失。您很有可能未将这些参数放入该文件,因为您正在测试这些值,尚未确定要使用哪个特定值。当然,您可以查看警报日志并找出所作的全部更改,但这一过程既费力又容易出错。
在 Oracle Database 11g 中,可以使用一个非常简单的命令,通过内存中的参数值来创建 pfile 或 spfile。
SQL> create spfile from memory; File created. SQL> create pfile from memory; File created.
# Oracle init.ora parameter file generated by instance ODEL11 on 08/28/2007 14:52:14
SQL> create pfile='/tmp/a' from memory;
在性能调优过程中,您要检查直接在 spfile 上更改几个动态初始化参数所造成的影响,突然您担心之后会找不到所作的更改。由于这些内容是在 spfile 中更改的,因此您不能指望 spfile 告诉您更改了哪些值。
没关系,可以使用以下方法:ALTER SYSTEM RESET 命令将值重置为默认值,覆盖您在 spfile 中所作的更改:
alter system reset license_max_sessions;
很多组织由于各种原因选择在一台服务器上运行多个数据库实例。无论何种原因,这通常都不是一个好主意。最佳替代方法是将所有这些应用程序整合到该服务器上运行的一个数据库中。这样做的结果是只存在一组 Oracle 后台进程并且只存在一组通用表空间(system、sysaux、undo、temp 等)。
相反,多个实例会各自要求自己的一组进程和存储,这会对硬件造成压力。更糟的是,一个实例可能完全独霸硬件的资源,如 CPU、I/O 通道和内存。可通过设置 SGA 大小来为某个实例保留实例内存,但是无法保留 PGA 内存。无法为特定实例保留 CPU 和 I/O(通过专门的 O/S 调节工具可能会有一些例外)。
在 Oracle Database 11g 第 2 版中,可以通过一个被称作实例囚笼的新特性在各实例间实现受限的 CPU 分配。(请注意,该限制适用于用户进程,但不适用于 pmon、smon、arch 等后台进程)这种分配通过数据库资源管理器和系统参数 cpu_count 来实现。默认情况下,实例通过查询操作系统中的 CPU 数量来设置 CPU_COUNT 参数。使用资源管理器,您可以将此参数更改为更小的值,从而将实例限制为使用指定数量的 CPU。
我们来看一下它的工作原理。首先,准备一个资源管理器计划。首先进行检查;您可能已经启用了一个资源管理器计划。该计划必须具有一个 CPU 指令,其他内容都不是必需的。
begin dbms_resource_manager.clear_pending_area(); dbms_resource_manager.create_pending_area(); dbms_resource_manager.create_plan ( plan => 'lim_1_cpu', comment => 'Plan to limit CPUs to just 1', max_iops => null, max_mbps => null ); dbms_resource_manager.create_plan_directive( plan => 'lim_1_cpu', group_or_subplan => 'OTHER_GROUPS', comment => 'Other Groups', mgmt_p1 => null, mgmt_p2 => 100 ); dbms_resource_manager.create_plan_directive( plan => 'lim_1_cpu', group_or_subplan => 'SYS_GROUP', comment => 'System Groups', mgmt_p1 => 50, mgmt_p2 => null ); dbms_resource_manager.submit_pending_area(); end; /
然后激活该计划:
SQL> alter system set resource_manager_plan = 'LIM_1_CPU'; System altered.
然后将 CPU 计数设为 1。
SQL> alter system set cpu_count = 1;
该指令现在将用户进程限制为一个 CPU(仅对所有用户进程)。切记,后台进程无论如何都将消耗所有 CPU。
相对于以前的 DBMS_JOB 功能,调度程序长期以来一直提供各种优势。现在您又多了一个喜欢它的理由。
当一个作业完成时,您如何得知?反复查询 DBA_SCHEDULER_JOBS 视图将会告诉您结果,但这几乎不具备实用性。更实用的解决方案是使用电子邮件。但是这带来了自身的一系列问题 — 从变更控制审批到无法修改源代码。
在 Oracle Database 11g 第 2 版中,您无需采用这些方法;有了更加好用的替代方法,即,调度程序会在作业完成后发送电子邮件。甚至还可在电子邮件中指出作业的完成是以成功还是失败结束。
为了进行演示,我们来创建一个运行名为 process_etl2 的存储过程的作业:
begin dbms_scheduler.create_job ( job_name => 'process_etl2', job_type => 'STORED_PROCEDURE', job_action => 'process_etl2', start_date => SYSTIMESTAMP, repeat_interval => 'freq=minutely; bysecond=0', enabled => TRUE); end; /
现在,为了启用电子邮件发送功能,我们必须设置一些与电子邮件相关的参数,如电子邮件服务器名称以及应以何种方式指定发件人的详细信息。
BEGIN dbms_scheduler.set_scheduler_attribute( 'email_server', 'mail.proligence.com:25' ); dbms_scheduler.set_scheduler_attribute( 'email_sender', 'dbmonitor@proligence.com' ); END; /
请注意,SMTP 服务器以 server[:port] 格式提供。如果未提供端口,将采用默认值 25。现在,我们可以向作业中添加电子邮件通知属性:
begin dbms_scheduler.add_job_email_notification ( job_name => 'process_etl2', recipients => 'arup@proligence.com', subject => 'Job: process_etl', body => 'ETL Job Status', events => 'job_started, job_succeeded'); END; /
该过程的参数具有自我说明性。一个非常重要的参数是 EVENTS,它指定在哪些事件发生期间应发送电子邮件。在该示例中,我们已指定在作业开始及作业成功时发送电子邮件(作业失败时不发送)。
EVENTS 参数可具有表值 job_failed、job_broken、job_sch_lim_reached、job_chain_stalled 和 job_over_max_dur,分别表示作业最终失败、作业损坏、作业已达到调度程序中的限制,作业所属的链已处于停滞状态以及作业超出了其持续时间。
如果要了解位于此通知系统下的各种作业所发送的电子邮件通知的状态,可以检查新数据字典视图 USER_SCHEDULER_NOTIFICATIONS。
SQL> desc user_scheduler_notifications Name Null? Type ----------------------------------------- -------- ---------------------------- JOB_NAME NOT NULL VARCHAR2(30) JOB_SUBNAME VARCHAR2(30) RECIPIENT NOT NULL VARCHAR2(4000) SENDER VARCHAR2(4000) SUBJECT VARCHAR2(4000) BODY VARCHAR2(4000) FILTER_CONDITION VARCHAR2(4000) EVENT VARCHAR2(19) EVENT_FLAG NOT NULL NUMBER
我们来看一下该视图的内容。
SQL> select job_name, recipient, event 2 from user_scheduler_notifications; JOB_NAME RECIPIENT EVENT ------------------------- -------------------- ------------------- PROCESS_ETL2 arup@proligence.com JOB_STARTED PROCESS_ETL2 arup@proligence.com JOB_SUCCEEDED
body 列显示所发送的实际邮件:
SQL> select BODY, event_flag 2 from user_scheduler_notifications 3 / BODY -------------------------------------------------------------------------------- EVENT_FLAG ---------- ETL Job Status 1 ETL Job Status 2
根据所定义的错误,电子邮件将持续不断。您还可以定义一个具有如下功能的筛选器:仅当错误代码为 ORA-600 或 ORA-7445 时才通知系统发送电子邮件。该表达式应该是一个有效的 SQL 谓词(没带“where”关键字的 WHERE 子句)。
BEGIN DBMS_SCHEDULER.add_job_email_notification ( job_name => 'process_etl2', recipients => 'arup@proligence.com', subject => 'Job: process_etl', body => 'ETL Job Status', filter_condition => ':event.error_code = 600 or :event.error_code = 7445', events => 'job_started, job_succeeded'); END; /
您是否注意到 body 只是一个简单的“ETL Job Status”?这个意义不大。用默认值代替它会更好。默认情况下,将发送具有以下正文的电子邮件通知
Job: %job_owner%.%job_name%.%job_subname% Event: %event_type% Date: %event_timestamp% Log id: %log_id% Job class: %job_class_name% Run count: %run_count% Failure count: %failure_count% Retry count: %retry_count% Error code: %error_code% Error message: %error_message%'
正如您所看到的,正文具有许多变量,如 %job_owner%。下表对这些变量进行了解释:
变量 | 描述 |
%job_owner% | 作业的所有者 |
%job_name% | 作业的名称 |
%job_subname% | 当作业是一个基于事件的作业时,该列显示事件链 |
%event_type% | 触发通知的事件,例如 job_broken、job_started 等 |
%event_timestamp% | 事件的发生时间 |
%log_id% | 作业执行的详细信息位于视图 DBA_SCHEDULER_JOB_LOG 和 DBA_SCHEDULER_JOB_RUN_DETAILS 中。这些视图中的 LOG_ID 列与该列相对应。 |
%error_code% | 错误代码(如果有的话) |
%error_message% | 错误消息(如果有的话) |
%run_count% | 该作业运行的次数 |
%failure_count% | 该作业失败的次数 |
%retry_count% | 该作业失败后重试的次数 |
要删除通知,可以使用同一软件包中的另一个过程:
begin dbms_scheduler.remove_job_email_notification ( job_name => 'process_etl2'); end; /
电子邮件通知使作业系统完整无缺。切记,电子邮件是由调度程序作业系统触发的,而不是由调度程序调用的程序或过程中的代码触发。这使您可以独立于作业下的实际代码来设置通知调度。
SQL*Plus 这个古老的工具除了在 Oracle Database 11g 第 2 版中有一个改动之外,近来并没有多大变化。新版本中,该工具的行为有了一个非常微小但具有潜在影响的更改。到目前为止,如果您执行一个事务但不发出显式提交或回滚,那么 SQL*Plus 会在事务结束时发出显式提交或回滚。如果是正常结束(即,您发出了退出或断开连接),那么会执行提交,否则将回滚事务。
在某些情况下,可能不需要这一默认行为。如果用户未发出显式提交,那么您可能希望在退出的情况下回滚事务。在第 2 版中,可以通过一个名为 exitcommit 的新参数来更改这一行为。我们来看一下它在两种不同 UNIX 提示符下的工作方式。
终端 1 | 终端 2 |
连接到 SQL*Plus # sqlplus arup/arup SQL> create table test (col1number); Table created. SQL> insert into test values (1); 1 row created. SQL> commit; Commit complete SQL> update test set col1 = 1 row updated. Note: a commit has not been issued yet. |
|
| 此时,检查来自这个不同会话的值。它应仍显示提交前的值。
SQL> select col1 from test; COL1 ---------- 1 1 row selected.
|
Exit from SQL*Plus normally: SQL> exit Disconnected from Oracle With the Partitioning, Automatic Storage Management,
|
|
| 检查该列的值:
SQL> select col1 from test; COL1 ---------- 2 1 row selected.
值现在为 2。为什么?因为 exit 语句自动发出了 commit 语句。 |
现在,将 exitcommit 参数设置为 OFF 并重复以上实验: SQL> set exitcommit off SQL> update test set col1 = 3; 1 row updated. SQL> exit 注:在 exit 语句之前未发出 commit 语句。
|
|
| 检查该会话中的值:
SQL> select col1 from test; COL1 ---------- 2
值仍为 2,也就是先前的值。由于设置了 exitcommit 参数,因此其他会话未发出 commit 语句。
|
但是,该参数仅影响 EXIT 语句。如果您仍在 SQL*Plus 中,但是只是通过发出 DISCONNECT 结束了会话,那么该参数没有任何效果。在这种情况下,仍将是提交的默认行为。在会话 1 中,将值更新为 4 之后,发出 disconnect 而非 exit。
SQL> disconnect On session 2, check the value SQL> select col1 from test; COL1 ---------- 4
由于 DISCONNECT 命令不受 EXITCMMIT 设置的影响,因此提交了操作。如果您在 SQL*Plus 中,但是希望连接到其他用户,您将发出:
SQL> connect differentuser/<password>
该操作发出隐式 DISCONNECT;因此将提交所有事务。您可能需要记住以下事实:exitcommit 仅影响 EXIT 语句;不影响 DISCONNECTS - 无论显式还是隐式。