使用Oracle RMAN脚本

为什么要使用脚本?

为什么要使用RMAN命令脚本呢? 这里有2个主要原因:

  1. 绝大多数RMAN操作都是批量的同时也是自动化的。举例来说,备份数据库是一个反反复复的操作而非每次执行都要费一番功夫写命令。
  2. 脚本提供了一致性。在临时性的抑或者说一次性的操作,譬如从备份中恢复数据库,一般都不适用自动化。但是,操作本身是相同的,无论DBA在何种环境下。

Oracle11g中有两种脚本形式

  1. 使用命令文件: 即文件系统上的一个文本文件,也叫平面文件。
  2. 使用存储的脚本,该脚本存储在Oracle恢复目录中,由RMAN命令行调用。

命令文件形式

Oracle 命令文件是一种文本解释文件类似于UNIX下的SHELL脚本和Windows中的批处理作业BAT文件。代码演示1中展示了一个非常简单的例子,用以备份USERS表空间。其中扩展名.rman是非必要的,但有助于帮您理清文件用途。

代码演示1:备份USERS表空间的脚本

connect target /

connect catalog rman/secretpass@rmancat

run {

allocate channel c1 type disk format ‘/orabak/%U’;

backup tablespace users;

}

你可以通过多种方式调用脚本,例如自RMAN提示行中:

RMAN> @backup_ts_users.rman

注意调用使用的符号@

您也可以在SHELL中调用脚本,例如:

rman @backup_ts_users.rman

这种方式十分有用,若不想使用@符号,用以下方式替代:

rman cmdfile=backup_ts_users.rman

注意CONNECT子句是放在backup_ts_users.rman命令文件中的,故再次无需提供用户名与密码,也保障了没有泄露密码的危险。

传递参数: backup_ts_users.rman命令文件运行地不错,但他太固定了。他将备份输出到某个特定目录且只备份一个表空间(USERS)。若你想要备份到另一处或其他表空间是,你需要创建新的脚本。

一个较好的策略是使用参数驱动的RMAN脚本。比起硬编码来,参数传递脚本灵活得多。代码演示2中展示了修改了的backup_ts_users .rman脚本。

代码演示2: 参数驱动脚本

connect target /

connect catalog rman/secretpass@rmancat

run {

allocate channel c1 type disk format ‘&1/%U’;

backup tablespace &2;

}

另一个SHELL脚本,名为backup_ts_generic.sh调用以上脚本,传递参数/tmp作为备份目录以及USERS为备份表空间对象。

$ORACLE_HOME/bin/rman <<EOF

@backup_ts_generic.rman “/tmp” USERS

EOF

你还可以使之更灵活,你可以将backup_ts_generic.sh第二行做以下修改:

@backup_ts_generic.rman “/tmp” $1

则您可以将参数传递给sh脚本,在以/tmp为固定备份目录的情况下。例如您要备份MYTS表空间:

backup_ts_generic.sh MYTS1

日志设置: 当你通过某种自动设置来运行RMAN脚本时,实际没有人在观察命令窗口,那我们如何找到RMAN的输出呢?这些输出日志使我们了解脚本的运行结果故之分重要,为了获取日志,我们可以使用log选项。

rman cmdfile=backup_ts_users.rman log=backup_ts_users.log

现在backup_ts_users.rman脚本的输出日志将会记录在名为backup_ts_users.log 的文件中而非出现在屏幕上。

存储脚本

虽然脚本文件可以满足大多数情况,但他们也有部分缺点。脚本文件总是存放在服务器上,这显得并不十分安全,因为只要有阅读该文件的权限就可以获取SYS等账号的密码。

解决方法就是使用RMAN的存储脚本,存储脚本保存在了Oracle恢复目录中,而非直接存放在服务器上。代码演示3展示了调用存储脚本的例子。

代码演示3

RMAN> run { execute script

backup_ts_users; }

C:\> rman

RMAN> connect target /

RMAN> connect catalog rman/secretpass@rmancat

RMAN> create script backup_ts_users

2> comment ‘Tablespace Users Backup’

3> {

4>      allocate channel c1 type disk format ‘c:\temp\%U’;

5>      backup tablespace users;

6> }

在代码演示3中存储脚本backup_ts_users仅在用户连接到目标数据库是可见。同时存储脚本也可以做到传递参数,如代码演示4

代码演示4:参数驱动的存储脚本:

RMAN> create script backup_ts_any

2> comment ‘Any Tablespace Backup’

3> {

4>      allocate channel c1 type disk format ‘c:\temp\%U’;

5>      backup tablespace &1;

Enter value for 1: users

users;

6> }

7>

调用参数驱动存储脚本时,我们需要使用USING 子句。如下例,使用存储脚本备份SYSTEM表空间:

run { execute script

backup_ts_any using ‘SYSTEM’; }

管理存储脚本:RMAN工具提供了管理存储脚本的必要方式。

可以用一下命令列出存储的脚本:

RMAN> list script names;

List of Stored Scripts in Recovery Catalog

Scripts of Target Database ARUPLAP1

Script Name

Description

————

backup_ts_any

Any Tablespace Backup

backup_ts_users

Tablespace Users Backup

以上命令会列出脚本的本地和全局名。

若只需要列出全局名,可以使用以下命令:

RMAN> list global script names;

若要显示某个脚本的具体内容,例如back_ts_any,可以使用以下命令:

RMAN> print global script

backup_ts_level1_any;

删除一个存储脚本:

RMAN> delete global script

backup_ts_level1_any;

你也可以通过一个脚本文件创建存储脚本,如以下:

RMAN> create script backup_ts_users

from file ‘backup_ts_users.rman’;

当然你也可以通过存储脚本还原出一个平面脚本文件:

RMAN> print script backup_ts_users

to file ‘backup_ts_users.rman’;

oracle express介绍

Oracle express 不是一种新型火车也不是一种新型的邮递服务。Oracle express是个多维的数据库和应用程序环境,这个应用程序环境是用来构建OLAP应用程序的。Express的组件(或者OLAP选项)是维(多维部分中的一部分)和变量。维是在Express数据库中分解出的逻辑单元。维是数据库的核心单元,它更像数据仓库表格的数维或者逻辑表达式的限制子句。product=TevaSandals,gender=male或者date_of_birth介于某个日期范围都是这样的字句示例。

变量是在Express数据库中包含数据的对象。这些变量不比用维描述的值(类似数据仓库的值,它的典型值是数字)的数组更重要。把这个数组的概念扩展一下,就可以把它看做表。把销售的变量看做维,可能就是Product ,Sales_District和Sales_dt_time。3个成为维的变量经常被称作数据立方体。

对Express数据库来说,具有多个变量,每个变量又是多维的情况并不常见(类似事件的一些维数通常是多个变量,而某些维对于变量是唯一的)。

Express数据库中的数据以这种多维方式存储,而不是典型的数据库结构,甚至以关系对象结构存储,因为这样会使用户处理回答他们业务问题的信息更快。因为业务经常需要以这种方式切片和切块数据,这就是他是OLAP数据库的原因。这种方式与那些在一般数据库中支持的方式是不同的。

Express有两个为支持无限制用户数而设计的功能。Express服务器为更大的应用程序而设计,它是个多维的数据储存器。Express服务器经常与已存在的数据仓库一起使用,或者作为一个功能全面的数据仓库的前身。根据需要,Express Server可替代数据仓库。Personal Express 是单用户的Express Server ,它由对数据做OLAP分析的个人使用,这样就不需要花费更大的代价和时间去执行一个功能全面的组织数据仓库。

还有很多这样的工具,他们作为Express服务器数据库产品的附加工具。Express Analyzer 是用来生成报告和分析Express服务器的工具。Express Objects 是支持开发Express数据库应用程序的OO工具。也可使用Express Web Publisher发布目录到Web上供别人查看。Express Spreadsheet是为Microsoft Excel和Express Sever数据库之间的接口提供的插件。

RMAN 内存利用介绍: PGA 以及SGA

在磁盘上的备份会使用PGA内存空间作为备份缓冲区,PGA内存空间从用于通道进程的内存空间中分配。如果操作系统没有配置本地异步I/O,可以利用DBWR_IO_SLAVES参数使用I/O从属来填充内存中的输出缓冲区。如果设置DBWR_IO_SLAVES参数为任意的非零值,RMAN会自动分配4个I/O从属来协调缓冲区内存中数据块加载。为了实现这一功能,RMAN必须利用一个共享内存区域。因此,用于磁盘备份的内存缓冲区会被推入共享池,如果存在Large池,则被推入large池。

如果没有使用磁带I/O从属,会在PGA中分配用于磁带输出缓冲区的内存。设置init.ora参数BACKUP_TAPE_IO_SLAVES=TURE,可以使用磁带I/O从属,必要时还可以在服务器参数文件(spfile)中动态设置该参数。BACKUP_TAPE_IO_SLAVES参数设置为TRUE时,RMAN会为每个通道创建一个从属进程来帮助备份工作。为了协调这一功能,RMAN会将内存分配推入SGA。

Oracle SGA中的Large池

Large池是Oracle内存空间的SGA中的一个特定区域。使用init.ora或SPFILE文件中的LARGE_POOL_SIZE参数可以设置large池,这个参数值被指定为一个字节数。对于某些需要共享空间且涉及共享池中常见操作的内存可以利用large池。占用large池的主要限于RMAN内存缓冲区(如果使用了I/O从属)和用于共享服务器。Large池又是用于java连接,如果PARALLEL_AUTOMATIC_TUNING (10g中不再使用)被设置为TRUE, large池还会包括并行查询从属(parallel query slave)

实际上,我们不一定需要large池。如果没有large池,所有可能占用large池的会简单地使用共享池中的空间。这不是世界末日,但是最好将RMAN缓冲区分到PGA中他们自己独立的空间中。这样以来,SQL和PL/SQL分析以及其他普通操作的共享池操作不会受到RMAN备份的影响。反之亦然,此外还可以更方便,更直接地调整RMAN的Oracle内存空间。

如果配置了任一种I/O从属选项并且没有配置large池,则会从SGA的共享池区中分配内存。如果没有配置large池却又要使用I/O从属,我们建议最好创建一个large池,这个large池的大小基于备份分配的通道总数(加上1MB用于开销)。

如果在磁带上做备份,就需要使用一个Media Management Server(介质管理服务器)产品。如果从与目标数据库相同的系统运行Media Manager(介质管理器),磁带子系统会需要额外的系统资源。调整备份时一定要考虑到这个因素。

部分行索引使用介绍

函数索引是Oracle索引中比较特殊的,我们这里讨论函数索引中部分行索引的使用。
部分行索引顾名思义仅就表中的一部分记录做索引,请看代码示例:

drop table test;

create table test  (t1 int, t2 char(1));
declare
i int :=0;
begin
while i<100000
loop

insert into test values( i, ‘N’);
i:=i+1;
commit;
end loop;
end;

在test表上插入大量t2为N的行,并插入少量t2为Y的行
create index ind_t2y on test( case t2 when ‘Y’ then t2 end);

SQL> select count(*) from test;

COUNT(*)
———-
100004

表上供有100004条数据

SQL> select count(*) from test where t2=’Y’;

COUNT(*)
———-
4
为t2列为’Y’的共有4条。

我们来分析该索引:

SQL> analyze index ind_t2y validate structure;

索引已分析

SQL> select lf_rows from index_stats;

LF_ROWS
———-
4

可以看到确实仅记录了4条记录。

我们尝试利用此部分行索引:

SQL> set autotrace on;
SQL> select count(*) from test where t2=’Y’;

COUNT(*)
———-
4

Execution Plan
———————————————————-
Plan hash value: 1950795681

—————————————————————————
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
—————————————————————————
|   0 | SELECT STATEMENT   |      |     1 |     3 |    43  (12)| 00:00:01 |
|   1 |  SORT AGGREGATE    |      |     1 |     3 |            |          |
|*  2 |   TABLE ACCESS FULL| TEST |     2 |     6 |    43  (12)| 00:00:01 |
—————————————————————————

Predicate Information (identified by operation id):
—————————————————

2 – filter(“T2″=’Y’)

Note
—–
– dynamic sampling used for this statement

Statistics
———————————————————-
0  recursive calls
0  db block gets
171  consistent gets
0  physical reads
0  redo size
515  bytes sent via SQL*Net to client
469  bytes received via SQL*Net from client
2  SQL*Net roundtrips to/from client
0  sorts (memory)
0  sorts (disk)
1  rows processed

没有如预期地使用索引,我们加上hint 再试试

SQL> select /*+ index(test ind_t2y) */ count(*) from test where t2=’Y’;

COUNT(*)
———-
4

Execution Plan
———————————————————-
Plan hash value: 2501600095

—————————————————————————————-
| Id  | Operation                    | Name    | Rows  | Bytes | Cost (%CPU)| Time     |
—————————————————————————————-
|   0 | SELECT STATEMENT             |         |     1 |     3 |     2   (0)| 00:00:01 |
|   1 |  SORT AGGREGATE              |         |     1 |     3 |            |          |
|*  2 |   TABLE ACCESS BY INDEX ROWID| TEST    |     2 |     6 |     2   (0)| 00:00:01 |
|   3 |    INDEX FULL SCAN           | IND_T2Y | 98705 |       |     1   (0)| 00:00:01 |
—————————————————————————————-

Predicate Information (identified by operation id):
—————————————————

2 – filter(“T2″=’Y’)

Note
—–
– dynamic sampling used for this statement

Statistics
———————————————————-
0  recursive calls
0  db block gets
2  consistent gets
0  physical reads
0  redo size
515  bytes sent via SQL*Net to client
469  bytes received via SQL*Net from client
2  SQL*Net roundtrips to/from client
0  sorts (memory)
0  sorts (disk)
1  rows processed

在使用部分行索引的情况下逻辑读大大下降了。
在不加hint的情况下优化器似乎永远无法做出正确的选择,即便修改了CBO相关的参数:

SQL> alter system set optimizer_index_cost_adj=1;

System altered.

SQL> select t2 from test where t2=’Y’;

T

Y
Y
Y
Y

Execution Plan
———————————————————-
Plan hash value: 1357081020

————————————————————————–
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
————————————————————————–
|   0 | SELECT STATEMENT  |      |     2 |     6 |    43  (12)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| TEST |     2 |     6 |    43  (12)| 00:00:01 |
————————————————————————–

仅在where 子句中指定了case when then模式时,优化器自觉地使用了该部分行索引:
SQL>  select * from test where case t2 when ‘Y’ then t2 end =’Y’;

T1 T
———- –
100001 Y
100002 Y
100003 Y
100004 Y

Execution Plan
———————————————————-
Plan hash value: 837354983

—————————————————————————————
| Id  | Operation                   | Name    | Rows  | Bytes | Cost (%CPU)| Time     |
—————————————————————————————
|   0 | SELECT STATEMENT            |         |     2 |    32 |     1   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| TEST    |     2 |    32 |     1   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | IND_T2Y |     1 |       |     1   (0)| 00:00:01 |
—————————————————————————————

Predicate Information (identified by operation id):
—————————————————

2 – access(CASE  WHEN “T2″=’Y’ THEN “T2” END =’Y’)

Note
—–
– dynamic sampling used for this statement

Statistics
———————————————————-
0  recursive calls
0  db block gets
4  consistent gets
0  physical reads
0  redo size
650  bytes sent via SQL*Net to client
469  bytes received via SQL*Net from client
2  SQL*Net roundtrips to/from client
0  sorts (memory)
0  sorts (disk)
4  rows processed

部分行索引在特定情况下(譬如某表中仅少数特定行有大量查询更新操作)可以发挥非常巨大的作用。

autotrace在绑定变量情况下不准确的问题

通常我们在检验SQL执行计划时采用autotrace的方法,但autotrace本身存在许多不准确的情况。
以下为一个例子:
SQL> create table test(t1 int, t2 char(200));

表已创建。

SQL> create index ind_t2 on test(t2);

索引已创建。

SQL> insert into test values (0,’A’);

已创建 1 行。

SQL> commit;

提交完成。

SQL> begin
2  for i in 1..100000 loop
3  insert into test values(i,’ZZZZ’);
4  end loop;
5  commit;
6  end;
7  /
SQL> analyze table test compute statistics ;

表已分析。

SQL> analyze index ind_t2 compute statistics;

索引已分析

SQL> analyze table test compute statistics for all indexed columns;

表已分析。

以上代码 在test表中 产生一条t2为A的记录以及10万条t2为ZZZZ的语句,即列上值出现严重的倾斜。
SQL> set autotrace on;
SQL> variable a char;
SQL> exec :a:=’A’;
SQL> alter system flush shared_pool;

系统已更改。
PL/SQL 过程已成功完成。

SQL> oradebug setmypid;
已处理的语句
SQL> oradebug event 10046 trace name context forever,level 10;
已处理的语句

SQL> select * from test where t2=:a;

T1
———-
T2
————————————————————————–
0
A

执行计划
———————————————————-
Plan hash value: 1357081020

————————————————————————–
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
————————————————————————–
|   0 | SELECT STATEMENT  |      | 50001 |  9961K|   652   (2)| 00:00:08 |
|*  1 |  TABLE ACCESS FULL| TEST | 50001 |  9961K|   652   (2)| 00:00:08 |
————————————————————————–

Predicate Information (identified by operation id):
—————————————————

1 – filter(“T2″=:A)

统计信息
———————————————————-
231  recursive calls
0  db block gets
38  consistent gets
0  physical reads
0  redo size
654  bytes sent via SQL*Net to client
385  bytes received via SQL*Net from client
2  SQL*Net roundtrips to/from client
4  sorts (memory)
0  sorts (disk)
1  rows processed

SQL> oradebug tracefile_name;
e:\oracle\product\10.2.0\admin\orclv\udump\orclv_ora_4956.trc

使用tkprof 工具对 trace文件整理
tkprof  e:\oracle\product\10.2.0\admin\orclv\udump\orclv_ora_4956.trc C:\ora_4956.trc

可以找到以上查询的实际执行计划。
select *
from
test where t2=:a

call     count       cpu    elapsed       disk      query    current        rows
——- ——  ——– ———- ———- ———- ———-  ———-
Parse        1      0.01       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.00          0          6          0           1
——- ——  ——– ———- ———- ———- ———-  ———-
total        4      0.01       0.01          0          6          0           1

Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: SYS

Rows     Row Source Operation
——-  —————————————————
1  TABLE ACCESS BY INDEX ROWID TEST (cr=6 pr=0 pw=0 time=43 us)
1   INDEX RANGE SCAN IND_T2 (cr=5 pr=0 pw=0 time=32 us)(object id 51539)

可以看到这里实际的执行计划时 INDEX RAGNE SCAN 而非TABLE ACCESS FULL,这是由于优化器(optimizer)实际使用了绑定变量窥视的手段,而autotrace工具似乎不具备这种特性,故其展现的执行计划出现严重偏差。

一般情况下autotrace的结果仍是准确的,但也仅是一般情况,这需要我们凭借直觉去分辨。

关于RAC中监听配置IP=FIRST的说明

为RAC自动配置的监听器(listener)一般都可以看到使用了IP=FIRST选项,以使得监听在所给出主机名的相关ip端点。默认情况下不使用IP=FIRST选项,监听器总是在所有该主机的网络接口上监听(listen)。

从8i开始,监听器在绑定Ip地址的方式已经改变。一般的规则是“在指定主机上监听所有端口(listen on all interfaces if a hostname is specified)。然而这仅在大多数情况下准确,在某些特定情况监听器可能并非如此表现。

你可能需要强制监听器仅绑定到特定的IP地址(即便在指定主机名的情况下),通过在监听配置文件LISTENER.ORA中配置IP=FIRST语句。

由于监听绑定端点对于数据库实例注册到监听尤为重要,所以我们有必要了解监听器具体如何决定绑定到TCP端点(endpoint)的算法。

常规算法

你可以尝试使用以下模式解释监听器如何绑定到TCP协议地址的表现:

(1) 如果你提供一个IP 地址,则监听器始终在该IP地址上监听。

(2) 若你提供一个主机名:

(a) Oracle 对 gethostbyname()函数 输入该主机名,可能返回多个IP地址。

gethostname 库函数会查询DNS服务器,/etc/hosts配置文件,和NIS服务以及其他方式,基于系统实际配置。具体如何工作依赖于操作系统类型,但一般来说/etc/host.conf,/etc/nsswitch.conf 和/etc/resolv.conf配置文件在UNIX平台上指导过程。’

你可以同过ping工具找出相关的IP(如例:ping <hostname> 活着 ping -s <hostname> 即可以看到主机名相关的IP)。 不要使用host,nslookup 或dig 工具,以上工具仅查询DNS服务可能返回错误结果。

(b) Oracle 将使用 gethostname函数得出当地系统配置的主机名。

gethostname() 库函数将返回本地系统的标准主机名。

你也可以通过hostname命令得到该值。

(c) Oracle 对 gethostbyname() 函数输入之前一步2b中得到的主机名。

(d)  Oracle 比较 2a 中与 2c中返回的IP, 若没有匹配的IP项,则监听器绑定到2a中返回的ip地址上。

或者

(e) 若有匹配项存在,则监听器绑定至所有工作的网络接口上。

你可以通过lsnrctl命令的输出非常容易地判断监听器是如何绑定到网络接口的,如下例:

$ lsnrctl status

LSNRCTL for Linux: Version 10.2.0.1.0 – Production on 07-MAY-2007 15:29:48

Copyright

(c) 1991, 2005, Oracle.  All rights reserved.

Connecting to (DESCRIPTION=(ADDRESS=(PROTOCOL=IPC)(KEY=EXTPROC1)))

STATUS

of the LISTENER

————————

Alias                     LISTENER

Version                   TNSLSNR for Linux:

Version 10.2.0.1.0 – Production

Start Date                13-APR-2007 14:11:16

Uptime                    24 days 1 hr. 18

min. 32 sec

Trace Level               off

Security                  ON: Local OS Authentication

SNMP

OFF

Listener Parameter File   /opt/oracle/product/10.2.0/db_1/network/admin/listener.ora

Listener Log File         /opt/oracle/product/10.2.0/db_1/network/log/listener.log

Listening

Endpoints Summary…

(DESCRIPTION=(ADDRESS=(PROTOCOL=ipc)(KEY=EXTPROC1)))

(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=10.10.10.10)(PORT=1527)))

(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=myhost.ro.oracle.com)(PORT=1521)))

在以上情况中,监听器绑定值指定IP10.10.10.10的1527端口上同时也在所有接口的1521端口上。

由于RAC情况中对于监听器监听范围的要求,故在给出主机名的情况下配置工具netca等,总是会在配置文件中加上IP=FIRST语句保证监听器绑定到2a中返回的IP地址而非所有网络接口。

参考文档:

metalink : 文档 ID:         300729.1

约束条件对于查询优化的作用

约束条件对于查询优化至关重要。 许多人仅仅认识到约束是为了保证数据的完整性,当然这也是对的。
但约束同事也会被优化器利用以便决定最优执行计划。
优化器使用以下数据作为输入变量:
1. 查询语句
2. 所有可用的数据库对象统计值
3. 系统统计值,可能存在的如CPU速度,单块物理读的速度,以及一系列硬件指标
4. 数据库初始化参数 (parameters)
优化器使用所有这些信息以便决定最好的查询方式。我常常遇到人们在数据仓库或报表系统中避免使用约束。
“或许他们不需要约束以保持数据完整性,但他们确实需要约束以获取最优执行计划。数据仓库中糟糕的执行计划
可能执行数个小时乃至于数天。由于性能考量,数据仓库同样需要约束!
让我来看一些例子(使用11gr1,11.1.0.7)。第一个例子是分区排除,该特性自版本7.3时引入。
在代码演示1中,我们建立2个表包括互斥的数据以及一个合并(UNION ALL)它们的视图。
代码演示1: 建立表以及互斥数据以及试图
SQL> create table t1
2  as
3  select * from all_objects
4  where object_type in (‘TABLE’,’VIEW’);

SQL> alter table t1 modify object_type not null;

表已更改。
SQL> alter table t1 add constraint t1_check_otype
2  check (object_type in (‘TABLE’,’VIEW’));

表已更改。

SQL> create table t2
2  as
3  select * from all_objects
4  where object_type in (‘SYNONYM’,’PROCEDURE’);

SQL> alter table t2 modify object_type not null;

表已更改。

表已创建。

SQL> alter table t2 add constraint t2_check_obype
2  check (object_type in (‘SYNONYM’,’PROCEDURE’));

表已更改。
SQL> create or replace view v
2  as
3  select * from t1
4  union all
5  select * from t2;

视图已创建。

代码演示2中我们使用object_type列查询视图并观察其执行计划。

代码演示2:
SQL> select * from v where object_type = ‘TABLE’;

Execution Plan
—————————————————————————-
Plan hash value: 3982894595

—————————————————————————–
| Id  | Operation            | Name | Rows  | Bytes | Cost (%CPU)| Time     |
—————————————————————————–
|   0 | SELECT STATEMENT     |      |    40 |  6320 |   151   (1)| 00:00:02 |
|   1 |  VIEW                | V    |    40 |  6320 |         (1)| 00:00:02 |
|   2 |   UNION-ALL          |      |       |       |            |          |
|*  3 |    TABLE ACCESS FULL | T1   |  3083 |   475K|    31   (0)| 00:00:01 |
|*  4 |    FILTER            |      |       |       |            |          |
|*  5 |     TABLE ACCESS FULL| T2   |     5 |   790 |   12    (1)| 00:00:02 |
—————————————————————————–

Predicate Information (identified by operation id):
——————————-

3 – filter(“OBJECT_TYPE”=’TABLE’)
4 – filter(NULL IS NOT NULL)
5 – filter(“OBJECT_TYPE”=’TABLE’)

在代码演示2中的执行计划似乎没有避免读取T2表,请注意T2表上的object_type仅包括SYNONYMS和PROCEDURES类型

。 但我们可以看到该读表操作的上层检查,即第四步是过滤操作,该过滤的方式是
NULL is NOT NULL
这十分有趣,我们并没有写过这样的句子,但优化器为我们添加了他。由于 NULL IS NOT NULL是恒假的,所以实际

上这系列操作(步骤4和5)其实永远不会发生。(你可以使用tkprof工具来确认这一点)

在下一个例子中,我们来体验一下为什么NOT NULL约束在涉及索引使用时特别重要。

SQL> create table t
2    as
3    select * from all_objects;
Table created.

SQL> create index t_idx on t(object_type);
Index created.

SQL> exec
dbms_stats.gather_table_stats( user, ‘T’ );
PL/SQL procedure successfully completed.

现在,我们尝试计算表中的行数,优化器仅有一种方案,如代码演示3
代码演示3:
SQL> set autotrace traceonly explain
SQL> select count(*) from t;

Execution Plan
—————————————-
Plan hash value: 2966233522

——————————————————————-
| Id  | Operation          | Name | Rows  | Cost (%CPU)| Time     |
——————————————————————-
|   0 | SELECT STATEMENT   |      |     1 |   283   (1)| 00:00:04 |
|   1 |  SORT AGGREGATE    |      |     1 |            |          |
|   2 |   TABLE ACCESS FULL| T    | 68437 |   283   (1)| 00:00:04 |
——————————————————————-
因为object_type列是可为空的且索引不包含空键值(指索引相关的所有列皆为空的情况),我们无法利用索引计算表

上的行数,故不得不全表扫描。若我们告知数据库,OBJECT_TYPE列是非空的,执行计划将立即改变,如代码演示4


代码演示4:
SQL> alter table t modify object_type NOT NULL;
Table altered.

SQL> set autotrace traceonly explain
SQL> select count(*) from t;

Execution Plan
——————————————
Plan hash value: 1058879072

————————————————————————
| Id  | Operation             | Name  | Rows   | Cost (%CPU)| Time     |
————————————————————————
|   0 | SELECT STATEMENT      |       |     1  |    54   (2)| 00:00:01 |
|   1 |  SORT AGGREGATE       |       |     1  |            |          |
|   2 |   INDEX FAST FULL SCAN| T_IDX | 68437  |    54   (2)| 00:00:01 |
————————————————————————
在object_type实际允许为空的情况呢?我们能做些什么呢?若我们可以在索引中加上非空的键值呢?显然计划将改

变。 在这里我把常数0加入索引。
SQL> drop index t_idx;
Index dropped.

SQL> create index t_idx
on t (object_type, 0);
Index created.

现在代码演示6中的执行计划趋向于使用索引了

代码演示6:
SQL> select * from t where object_type is null;

Execution Plan
—————————–
Plan hash value: 470836197

————————————————————————————–
| Id  | Operation                    | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
————————————————————————————–
|   0 | SELECT STATEMENT             |       |     1 |   101 |  1      (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID | T     |     1 |   101 |  1      (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN           | T_IDX |     1 |       |  1      (0)| 00:00:01 |
————————————————————————————–

Predicate Information (identified by operation id):
—————————————————
2 – access(“OBJECT_TYPE” IS NULL)

约束,主键,以及外键

现在我们来测试主键,外键是如何影响优化器的。我们复制scott.emp和scott.dept表使用

DBMS_STATS.SET_TABLE_STATS改变他们的统计量使得它们看起来“十分庞大”,让优化器产生这种假象。

代码演示7:

SQL> create table emp
2    as
3    select *
4    from scott.emp;
Table created.

SQL> create table dept
2    as
3    select *
4    from scott.dept;
Table created.

SQL> create or replace view emp_dept
2    as
3    select emp.ename, dept.dname
4      from emp, dept
5     where emp.deptno = dept.deptno;
View created.

SQL> begin
2       dbms_stats.set_table_stats
3           ( user, ‘EMP’, numrows=>1000000, numblks=>100000 );
4       dbms_stats.set_table_stats
5           ( user, ‘DEPT’, numrows=>100000, numblks=>10000 );
6    end;
7    /
PL/SQL procedure successfully completed.

我们同样使用一个view:emp_dept来返回以上2个表的连接查询结果,在该view仅返回emp表上数据时(ename列),我们

发现执行计划需要读取emp与dept两个表,如代码演示8。

代码演示8:
SQL> select ename from emp_dept;

Execution Plan
—————————–
Plan hash value: 615168685

—————————————————————————————-
| Id   | Operation          | Name  | Rows  |  Bytes |TempSpc | Cost (%CPU) | Time     |
—————————————————————————————-
|    0 | SELECT STATEMENT   |       |  1000K|     31M|        | 31515    (1)| 00:06:19 |
|*   1 |  HASH JOIN         |       |  1000K|     31M|   2448K| 31515    (1)| 00:06:19 |
|    2 |   TABLE ACCESS FULL| DEPT  |   100K|   1269K|        |  2716    (1)| 00:00:33 |
|    3 |   TABLE ACCESS FULL| EMP   |  1000K|     19M|        | 27151    (1)| 00:05:26 |
—————————————————————————————-

Predicate Information (identified by operation id):
—————————————————
1 – access(“EMP”.”DEPTNO”=”DEPT”.”DEPTNO”)

我们知道实际无需访问dept表,由于deptno是dept表的主键,而emp表中的dept实际是外键。现在我们加上这层约束

关系。
SQL> alter table dept add constraint
dept_pk primary key(deptno);
Table altered.

SQL> alter table emp add constraint
emp_fk_dept foreign key(deptno)
2    references dept(deptno);
Table altered.

试看代码演示9中的执行计划

代码演示9:
SQL> select ename from emp_dept;

Execution Plan
——————————
Plan hash value: 3956160932

————————————————————————–
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
————————————————————————–
|   0 | SELECT STATEMENT  |      | 50000 |   976K| 27152   (1)| 00:05:26 |
|*  1 |  TABLE ACCESS FULL| EMP  | 50000 |   976K| 27152   (1)| 00:05:26 |
————————————————————————–

Predicate Information (identified by operation id):
—————————————————
1 – filter(“EMP”.”DEPTNO” IS NOT NULL)

如上dept表在以上查询中无需再在考虑之内了,故也谈不上哈希连接了,多出了一个断言:deptno是非空的。
优化器意识到外键和逐渐的存在,以上查询实际等效于 SELECT ENAME FROM EMP WHERE DEPTNO IS NOT NULL。
优化器舍弃了不必要的表,获得了响应时间上的进步。
同时也证明了实际应用中不因该总是使用SELECT *以简化应用的实施。

NULL对于索引的影响:

Indexes and NULLs

Applies to:

Oracle Server – Enterprise Edition – Version: 9.2.0.8 to 10.2.0.4 – Release: 9.2 to 10.2
Information in this document applies to any platform.

Purpose

This article illustrates some common reasons why indexes are not selected when NULLs are present.

Scope and Application

This is a basic level overview with examples of index usage.

Indexes and NULLs

Indexes and NULLs

When dealing with indexes, a common mistake is to forget about NULLs. Indexes do not store NULL values and so indexes on NULLable columns can’t be used to drive queries unless there is something that eliminates the NULL values from the query.

To illustrate this are a number of examples based upon the following table/indexes:

drop table nulltest; create table nulltest ( col1 number, col2 number, col3 number not null, col4 number not null); create index nullind1 on nulltest (col1); create index notnullind3 on nulltest (col3); begin for i in 1..10000 loop insert into nulltest values (i,i,i,i); if i mod 1000 = 0 then commit; end if; end loop; end; / analyze table nulltest compute statistics;

Illustrative Queries:

select col1 from nulltest t; select /*+ index(t nullind1) */ col1 from nulltest t; select /*+ index(t) */ col1 from nulltest t; select /*+ index(t notnullind3) */ col1 from nulltest t; select /*+ index(t notnullind3) */ col3 from nulltest t; select /*+ index(t nullind1) */ col1 from nulltest t where col1 between 0 and 20000; select col1 from nulltest t where col1 is not null;

Queries and Explanations:

SQL> select col1 from nulltest t; Execution Plan ---------------------------------------------------------- 0 SELECT STATEMENT Optimizer=CHOOSE (Cost=6 Card=10000 Bytes=30000) 1 0 TABLE ACCESS (FULL) OF 'NULLTEST' (Cost=6 Card=10000 Bytes=30000)

col1 is NULLable so the index cannot be used with no predicate
SQL> select /*+ index(t nullind1) */ col1 from nulltest t; Execution Plan ---------------------------------------------------------- 0 SELECT STATEMENT Optimizer=CHOOSE (Cost=6 Card=10000 Bytes=30000) 1 0 TABLE ACCESS (FULL) OF 'NULLTEST' (Cost=6 Card=10000 Bytes=30000)
hinting the index on col1 (nullind1) makes no difference since col1 is NULLable
SQL> select /*+ index(t) */ col1 from nulltest t; Execution Plan ---------------------------------------------------------- 0 SELECT STATEMENT Optimizer=CHOOSE (Cost=49 Card=10000 Bytes=30000) 1 0 TABLE ACCESS (BY INDEX ROWID) OF 'NULLTEST' (Cost=49 Card=10000 Bytes=30000) 2 1 INDEX (FULL SCAN) OF 'NOTNULLIND3' (NON-UNIQUE) (Cost=20 Card=10000)

An open index hint on the table allows the selection of the index on the NOT NULL column (col3). Notice that the col3 predicate is not included anywhere in the query. In order for col1 to be retrieved, the table has to be accessed.

SQL> select /*+ index(t notnullind3) */ col1 from nulltest t; Execution Plan ---------------------------------------------------------- 0 SELECT STATEMENT Optimizer=CHOOSE (Cost=49 Card=10000 Bytes=30000) 1 0 TABLE ACCESS (BY INDEX ROWID) OF 'NULLTEST' (Cost=49 Card=10000 Bytes=30000) 2 1 INDEX (FULL SCAN) OF 'NOTNULLIND3' (NON-UNIQUE) (Cost=20 Card=10000)
hinting notnullind3 directly works as well
SQL> select /*+ index(t notnullind3) */ col3 from nulltest t; Execution Plan ---------------------------------------------------------- 0 SELECT STATEMENT Optimizer=CHOOSE (Cost=20 Card=10000 Bytes=30000) 1 0 INDEX (FULL SCAN) OF 'NOTNULLIND3' (NON-UNIQUE) (Cost=20 Card=10000 Bytes=30000)
Selecting the NOT NULL column (col3) works fine and uses the index with no table access.
SQL> select /*+ index(t nullind1) */ col1 from nulltest t where col1 between 0 and 20000; Execution Plan ---------------------------------------------------------- 0 SELECT STATEMENT Optimizer=CHOOSE (Cost=20 Card=10000 Bytes=30000) 1 0 INDEX (RANGE SCAN) OF 'NULLIND1' (NON-UNIQUE) (Cost=20 Card=10000 Bytes=30000)
The effect of the predicate against col1 is to eliminate nulls from the data returned from the column. This allows the index to be used.
SQL> select col1 from nulltest t 2 where col1 is not null; Execution Plan ---------------------------------------------------------- 0 SELECT STATEMENT Optimizer=CHOOSE (Cost=5 Card=10000 Bytes=30000) 1 0 INDEX (FAST FULL SCAN) OF 'NULLIND1' (NON-UNIQUE) (Cost=5 Card=10000 Bytes=30000)
This example illustrates that forcing the column to return only NOT NULL values allows the index to be used.

Note that in the previous example, the Index hint prevents an index fast full scan operation from being selected. An INDEX_FFS hint must be supplied to force an index fast full scan.

内部视图:interval view x$kvii 介绍

内部视图x$kvii

554078    kslerb    event range base
873    kslnbe    # of base events
285    kslnbesess    # of base events in session
382    kslltl    number of latches
2    ksbcpu_static    initial number of CPUs in the system
4096    kcbswc    DBWR max outstanding writes
1    kcbnwp    number of DBWR processes
204    kcbscw    DBWR write chunk
1    kctsat    true if Statically Allocated Thread
1    kctthr    THRead mounted by this instance – zero if none
1    ktsinm    sga shadow value of instance_number
0    rfragns    Global request id for site

其中kslltl为Oracle中父闩的数量,即kslltl=select count(*) from v$latch;

ksbcpu_static记录了当前cpu的内核总数。

kcbswc为Oracle单位时间内所能写出的最大块数Largest # blocks you can write at any given time)实际受限于物理写出能力。

kctthr为当前实例的线程号。

关于这些视图的研究仍有诸多不明确。

附Oracle kernel 层次:

Kernel Subsystems:

OPI Oracle Program Interface
KK Compilation Layer – Parse SQL, compile PL/SQL
KX Execution Layer – Bind and execute SQL and PL/SQL
K2 Distributed Execution Layer – 2PC handling
NPI Network Program Interface
KZ Security Layer – Validate privs
KQ Query Layer
RPI Recursive Program Interface
KA Access Layer
KD Data Layer
KT Transaction Layer
KC Cache Layer
KS Services Layer
KJ Lock Manager Layer
KG Generic Layer
KV Kernel Variables (eg. x$KVIS and X$KVII)
S or ODS Operating System Dependencies

关于Oracle中supplemental log的补充说明

在上一篇关于Oracle补全日志的介绍中漏写了关于最小补全日志(minimal supplemental log)与表级补全日志的关系;表级补全日志需要在最小补全日志打开的情况下才起作用,即若一个数据库没有开最小补全日志或之前drop supplemental log data操作则即便指定了表级补全日志,实际在重做日志输出的过程中描述的记录仍只记录rowid和相关列值。

打开最小补全日志的命令如下:

在上一篇关于Oracle补全日志的介绍中漏写了关于最小补全日志(minimal supplemental log)与表级补全日志的关系;表级补全日志需要在最小补全日志打开的情况下才起作用,即若一个数据库没有开最小补全日志或之前drop supplemental log data操作则即便指定了表级补全日志,实际在重做日志输出的过程中描述的记录仍只记录rowid和相关列值。

打开最小补全日志的命令如下:

Alter database add supplemental log data;

其次若如之前叙述的因表上的列数过多(超过200个),则应检查视图 dba_logstdby_not_unique, 该视图记录了在数据库中没有主键或没有唯一索引并且列非空的索引(tables in the primary database that do not have a primary key or unique index with NOT NULL columns)的表。如使用以下SQL:

select owner, table_name, bad_column

from dba_logstdby_not_unique

where table_name not in

(select table_name from dba_logstdby_unsupported);

TSMSYS    SRS$    Y
HTEST    TEST    N
HGET    GETMAXID    N
HGET    HUSER    N
SCOTT    BONUS    N
SCOTT    SALGRADE    N

其中bad_column列较为关键。若该字段为 Y,表示一个表列被使用大数据类型定义,例如CLOB或BLOB。sql apply尝试维护这些表,但是你必须要保证表中除这列外的其他列的单值性。就是说,注意,如果一个表中有两行除了LOB列外,其他的值完全相同,这样表 的改动就不能被逻辑备用数据库应用,sql apply会停止。N,表示表中包含足够的列信息,需要用来在逻辑备用数据库中维护表的。

针对前文叙述的在表上列较多的情况下(超过200个列),且不能添加主键和唯一非空索引的表,我们需要特别关注。但实际如果我们想了解一个段在一定段内产生的重做量却十分困难。(method :check  how much redo generated by one segment)

已知的研究方法例如logmnr工具,和dump redologs以及oradebug都无法提供足够的信息帮助统计。

仅有的方法是通过logmnr估算,v$logmnr_contents视图中记录的rbablk与rbabyte,为重做日志中的块偏移量(redo log中512byte为一个快)与字节偏移量,通过计算差值结合data_obj#列,可以大致估算某个段上一定时间内的重做量:

create table redo_analysis nologging as

select data_obj#,  oper, rbablk*512 + rbabyte curpos,

lead(rbablk*512+rbabyte,1,0) over (order by  rbasqn, rbablk, rbabyte)

nextpos

from

( select distinct data_obj#,  operation oper,

rbasqn, rbablk, rbabyte from v$logmnr_contents

order by rbasqn, rbablk, rbabyte );

select data_obj#, oper, obj_name, sum(redosize) total_redo

from

(

select data_obj#, oper, obj.name obj_name , nextpos-curpos-1 redosize

from redo_analysis redo1, sys.obj$ obj

where (redo1.data_obj# = obj.obj# or  redo1.data_obj# = obj.dataobj#)

and  nextpos !=0 — For the boundary condition

union all

select data_obj#, oper, ‘internal ‘ , nextpos-curpos  redosize

from redo_analysis redo1

where  redo1.data_obj#=0 and  redo1.data_obj# = 0

and nextpos!=0

)

group by data_obj#, oper, obj_name

order by 4

以上估算并不准确,在有手动切换(switch logfile)日志及其他特殊情况时误差较大。

ORA-12500内存耗尽一例

3月8日下午发现主机130.31.1.234无法登录,尝试登录Oracle,系统返回ORA-12500错误(TNS:listener failed to start a dedicated server process)。可能引起该错误的原因有多种,包括以下:

Oracle服务进程使用的session或process数达到了参数设置的上限,导致无法再分配新的服务进程。

系统资源耗尽,Oracle在启动新进程时调用的系统调用fork函数因资源不足而出错。

AIX下sys0对象上的属性maxuproc代表用户可以使用的最大进程数,若用户进程数接近该设定值可能导致Oracle无法启动新进程。

因主机无法远程登录,故在晚间进行了重启。重启后查看Oracle告警日志发现记录:“skgpspawn failed:category = 27142, depinfo = 12, op = fork, loc = skgpspawn3”,该段信息表示模块skgpspawn在fork一个新进程是出现了错误;由此可以判断不是由于session或process数达到参数设置的上限,因为若是session或process数不足,Oracle应当显示地返回ORA-00018(maximum number of sessions exceeded)或ORA-00020(maximum number of processes (%s) exceeded)错误。

通过在主机上查看sys0对象属性,发现maxuproc参数的设置值达到了4096,故基本可以排除因该参数不当引起连接问题的可能性。

系统资源耗尽将导致Oracle监听器无法为新的连接分配新的服务进程,而老的服务进程上的内存资源等可能一直没有得到释放;statspack是Oracle9i中反映Oracle运行性能的工具,以JOB形式在后台运行,目前设置为每两小时运行一次快照。分析快照发现,在主机重启前最后一次快照为下午14:15分开始的,之后系统进入资源紧张阶段,Oracle无法分配新的JOB(j00n)进程,故最后的快照发生在系统出现问题之前。

分析快照内容,在12:06到14:15之间,数据库参数没有改动的记录,sga_max_size设置为10GB, pga_aggregate_target设置为4GB,考虑到Oracle在启用RAC特性后SGA的实际内存使用量将会超过sga_max_size的设置,故Oracle总的内存最大使用量应控制在20GB内。而目标主机的实际物理内存达到了64GB,且专业计费系统一直以来运行良好,故可以排除因Oracle内存参数设置不当,而造成了本次问题的出现。

进一步分析快照发现这一阶段内Oracle数据库高速缓冲的命中率buffer hit为55.90,这个值要较平时水平低很多,可以判断该阶段内数据库可能执行了一些不同于日常业务的操作,这些操作引起较大的物理读表现为缓冲池的命中率明显降低。分析等待事件可以发现,db file scattered read事件即数据库多块物理读是这一阶段的主要等待事件,进一步印证了上述的判断。

通过对数据库快照的分析,证实在连接问题发生之前的短暂时间内,在P6702实例上的确有过引起较大物理读的操作,但实际Oracle使用的内存受到sga_max_size与pga_aggregate_target参数的限制应控制在20GB的范围内,且专业计费系统数据库使用裸设备数据文件,不存在过度使用文件系统缓存的可能,故可以排除由Oracle数据库导致系统资源耗尽的可能性。

因为没有该阶段内系统内存使用量的日志文件,故无法了解到目标主机上当时其他服务的实际内存使用量,但可以排除是问题因Oracle引起的。

沪ICP备14014813号-2

沪公网安备 31010802001379号