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

约束条件对于查询优化至关重要。 许多人仅仅认识到约束是为了保证数据的完整性,当然这也是对的。
但约束同事也会被优化器利用以便决定最优执行计划。
优化器使用以下数据作为输入变量:
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引起的。

Oracle rac进阶管理专家指导系列文档

Session1

Session2

Session3

Session4

Session5

Session6

Session7

Session8

Oracle恢复目录的管理使用简要

I. 使用恢复目录存储RMAN备份记录

  1. Oracle 官方建议把恢复目录建议于独立的数据库中。如果把恢复目录与其他一些数据混杂在某库中,若该库失败则恢复目录一起丢失,这将导致恢复异常困难。
  2. 在恢复目录中登记某个库被称作注册(registration).可以在恢复目录中注册多个目标库。举例来说,你可以注册数据库 prod1,prod2,和prod3在一个单独的由用户catowner拥有的目录中,而该目录位于一个叫catdb的数据库中。 因为RMAN通过DBID即数据库的身份证来分辨各个库。每个在恢复目录中注册过的目标库都有一个唯一的DBID.
  3. 恢复目录主要包括以下RMAN的使用情况信息:

l  数据文件和归档日志的备份集和备份片

l  数据文件的拷贝

l  归档日志及其拷贝

l  目标库中的表空间和数据文件

l  储存的脚本

l  RMAN的永久性配置

  1. 恢复目录保存了目标库控制文件中重要的RMAN操作原数据。同步恢复目录保证与控制文件中当前信息同步。
  2. RMAN 创建快照控制文件,即临时控制文件,当每次需要做全局同步时。快照临时文件保证了RMAN同步时的一致性读。数据库服务进程保证同时只有一个快照临时文件的存在,这对于保证RMAN操作不受其他进程干扰是必要的。
  3. 丢失恢复目录将导致严重的恢复问题。如何备份恢复目录可参考一般数据库的备份方式。
  4. 关于恢复目录的兼容性,可以通过查询恢复目录用户模式下的rcver表了解参与恢复目录使用端的版本号,示例:
SQL> SELECT * FROM rcver;

VERSION
------------
08.01.05.00
09.00.01.00
10.02.01.00

只要是8i之后版本一般不存在兼容性问题。

II 管理恢复目录

创建恢复目录

管理恢复目录中的目标库记录

同步恢复目录

恢复目录模式下的控制文件管理

备份恢复目录

导入和导出恢复目录

增强恢复目录可用性

查询恢复目录视图

更新恢复目录

删除恢复目录

  1. 创建恢复目录,创建恢复目录分成三步:
  • 配置恢复目录所在数据库
  • 创建恢复目录拥有者
  • 创建恢复目录本身

配置恢复目录数据库

若使用恢复目录,RMAN要求维护恢复目录所在模式。恢复目录储存在当前模式的默认表空间中,注意SYS不能是恢复目录的拥有者。我们强烈建议恢复目录数据库使用归档模式。同时必须分配足够的空间给恢复目录所在模式,恢复目录所占用的空间取决于使用恢复目录的目标数据库的数量。适当地为恢复目录库规划容量是必要的。应当保证恢复目录库和目标数据库的不占用同一磁盘。

创建目录拥有者

在合理配置恢复目录库后,我们来创建目录拥有者

使用目录库上的SYS帐号登录

假定当前有一个tool表空间来保存目录

使用temp临时表空间为用户默认临时表空间

如下步骤:

     CONNECT SYS/oracle@catdb AS SYSDBA
 SQL> CREATE USER rman IDENTIFIED BY cat
       TEMPORARY TABLESPACE temp
       DEFAULT TABLESPACE tools
    QUOTA UNLIMITED ON tools;

同时我们要授予 recovery_catalog_owner 权限给用户,该角色拥有管理创建恢复目录的权限。

   SQL> GRANT RECOVERY_CATALOG_OWNER TO rman;

创建恢复目录

在创建恢复目录用户后,使用RMAN建立恢复目录,操作如下:

$ rman

RMAN> CONNECT CATALOG rman/cat@catdb   --以目录用户连接恢复目录库

RMAN> create catalog                         — 建议恢复目录

当然也可以指定使用的表空间:

RMAN> create catalog  tablespace users;

成功建立恢复目录后,可以查询目录下已经存在的目录使用的基表。

SQL>select table_name from user_tables;

2. 管理恢复目录中的目标库记录

ü  在恢复目录中注册目标数据库

ü  在恢复目录中注销目标数据库

ü  在恢复目录中重置数据库

ü  在恢复目录中移除已删除的记录

在恢复目录中注册目标数据库

首先确定恢复目录库已经打开,从目标库主机登录:

$ rman TARGET / CATALOG rman/cat@catdb

若目标库未启动,首先启动到加载模式:

RMAN> STARTUP MOUNT;

注册目标库:

RMAN> REGISTER DATABASE;

RMAN会自动在恢复目录中记录目标库的各种信息,将目标库控制文件中的

元信息复制到恢复目录中,可以使用以下命令确认注册情况:

RMAN> REPORT SCHEMA;

Report of database schema
File Size(MB)   Tablespace       RB segs Datafile Name
---- ---------- ---------------- ------- -------------------
1        307200 SYSTEM             NO    /oracle/oradata/trgt/system01.dbf
2         20480 UNDOTBS            YES   /oracle/oradata/trgt/undotbs01.dbf
3         10240 CWMLITE            NO    ...

在恢复目录中登记备份文件

若有备份文件未在控制文件或恢复目录中存在对应的记录,则需要登记该文件,此处的(control file 为目标数据库control file)。

示例:

RMAN> CATALOG DATAFILECOPY '/disk1/old_datafiles/01_01_2003/users01.dbf';
RMAN> CATALOG ARCHIVELOG '/disk1/arch_logs/archive1_731.dbf',
     '/disk1/arch_logs/archive1_732.dbf';
RMAN> CATALOG BACKUPPIECE '/disk1/backups/backup_820.bkp';

在恢复目录中登记多个目标库

可以在一个恢复目录中注册多个目标库,前提是目标库的DBID唯一。

在恢复目录中注销目标库

可以使用命令: unregister database 在RMAN中注销目标数据库。当数据库被

注销,所有的RMAN记录都会丢失,所以要小心操作。

在恢复目录中移除已经删除的记录

在9i之后版本,RMAN在删除备份文件的同时会删除在恢复目录中的对应物记

录,而9i以前版本则只将对应物记录标志为delete.可以通过运行脚本

prgrmanc.sql来删除对应物记录,该脚本储存在($ORACLE_HOME/rdbms/admin)

录下。示例如下:

% sqlplus rman/cat@catdb
SQL> @?/rdbms/admin/prgrmanc.sql删过期备份信息

同步恢复目录

当恢复目录当前状态晚于数据库控制文件中的备份信息时,则需要使用同步恢复

目录,这种情况只会出现在一段时间使用恢复目录而一段时间不使用恢复目录的

情况下,造成的时间段差异。RMAN会在您做某些操作时自动完成同步,例如

Backup命令,当然你也可以手动同步: resync catalog .

管理控制文件

数据库参数CONTROL_FILE_RECORD_KEEP_TIME决定了控制文件中记录可能被复

用的最短自然天数,因此你保证恢复目录在此期间完成同步,否则可能控制文件

中的记录丢失,则需要手动登记备份文件。CONTROL_FILE_RECORD_KEEP_TIME有

效期内需要定期同步。

备份恢复目录

备份恢复目录数据库十分重要,若恢复目录数据库丢失则所有的备份信息将丢

失,导致恢复十分困难。

备份恢复目录数据库与一般的数据库没有大的区别,以下为注意事项:

恢复目录数据库因该运行在归档模式下

使用备份策略冗余量大于一

在不同的介质上备份

不使用恢复目录记录备份信息

使用控制文件自动备份,rman中可以自动完成

结构图:

catalog

更新恢复目录

若您使用的恢复目录版本低于使用的客户端,则您需要更新恢复目录。举例来说

当前您使用了8.1版的客户端RMAN,而恢复目录是8.0版本的,则需要更新。

当恢复目录版本高于您使用的客户端,则upgrade catalog报错。更新操作实例如

下:

sqlplus> connect sys/oracle@catdb as sysdba;
sqlplus> grant TYPE to rman;
% rman TARGET / CATALOG rman/cat@catdb
UPGRADE CATALOG;

recovery catalog owner is rman

enter UPGRADE CATALOG command again to confirm catalog upgrade

UPGRADE CATALOG;

recovery catalog upgraded to version 09.02.00
DBMS_RCVMAN package upgraded to version 09.02.00
DBMS_RCVCAT package upgraded to version 09.02.00






删除恢复目录

当恢复目录不在需要时可以在所在数据库中彻底删除目录结构和数据,删除将丢

失所有注册过的备份信息,操作要小心。示例操作:

% rman TARGET / CATALOG rman/cat@catdb

Issue the DROP CATALOG command twice to confirm:

DROP CATALOG;

recovery catalog owner is rman
enter DROP CATALOG command again to confirm catalog removal

DROP CATALOG;

延迟块清除介绍

在Oracle中数据锁(这里主要指TX类型行锁)实际上是数据的属性,存储在块首部,称之为事务槽(ITL)。COMMIT操作的职责包括释放块上的锁,实际的释放方式即清除块上相应的事务槽,但这里存在一个性能的考量。设想一个UPDATE大量数据的操作,因为执行时间较长,一部分已修改的块已被缓冲池flush out写至磁盘,当UPDATE操作完成执行COMMIT操作时,则需要将那些已写至磁盘的数据块重新读入,这将消耗大量I/O,并使COMMIT操作十分缓慢;为了解决这一矛盾,Oracle使用了延迟块清除的方案,对待存在以下情况的块COMMIT操作不做块清除:

在更新过程中,被缓冲池flush out写至磁盘的块

若更新操作涉及的块超过了块缓冲区缓存的10%时,超出的部分块。

虽然COMMIT放弃对这些块的块清除(block cleanout)操作,但COMMIT操作仍会修改回滚段的段头,回滚段的段头包括了段中的事务的字典,COMMIT操作将本事务转化为非ACTIVE状态。

当下一次操作如SELECT,UPDATE,INSERT或DELETE访问到这些块时可能需要在读入后完成块清除,这样的操作称之为块延迟清除(deferred block cleanout);块延迟清除通过事务槽上的回滚段号,槽号等信息访问回滚段头的事务字典,若事务不再活跃或事务过期则完成清除块上的事务槽,事务槽清除后继续执行相应的操作。

块延迟清除的影响在SELECT操作过程中体现的最为明显。总结来说块延迟清除是COMMIT操作的一个延续,始终是一种十分轻微的操作,且该种操作是行级的,不会使段(Segment)的属性有所改变。

同样的查询每次都产生大量物理读的调优示例

12月中旬用户反映综合传输网管库上的一个查询影响迟缓,具体现象表现为当多个用户在应用界面上同时点下查询后,结果返回耗时长,影响正常业务的运作。经过初步分析该操作主要的等待事件在db file sequential read上,为了进一步明确问题,我们在系统的高峰时段使用性能报告工具抓取了统计信息,以下为top3等待事件:

Event                               Waits   Timeouts   Time (s)   (ms)

—————————- ———— ———- ———- ——

enqueue                           542        402      1,406   2595

db file sequential read           446,099          0        391      1

db file scattered read            156,634          0        209      1

可以看到db file sequential read事件仅次于数据库队列事件为主要的数据库性能瓶颈,以下列出缓存占用较高的典型SQL:

Selecta.objectid,a.emsalarm_time,a.emsend_time,c.label_cn,c.alias,a.alarm_name,a.alm_devinfo from traph c,alarm_to_traph b,current_alarm a where a.cuid=b.related_alarm_cuid and b.related_traph_cuid=c.cuid and (c.ext_ids=’,8,’ or c.ext_ids=’,9,’ or c.ext_ids=’,12,’ or c.ext_ids=’,19,’ or c.ext_ids=’,25,’) and a.emsend_time is null and a.emsalarm_time > to_date(‘2008-11-19′,’yyyy-MM-dd’)

经过进一步追踪我们发现以上SQL正是应用界面点击查询所做的操作,这就明确了此次优化的主要目的,即分析并尽可能降低该语句所产生的物理读和逻辑读,保证应用的正常运行。

具体分析
该句查询语句涉及到三个表的连接,因为应用设计使用的是基于RULE的优化模式,故执行计划倾向于使用索引来代替全表扫描,在表与表的连接方式上倾向使用嵌套循环即(NESTED LOOP),具体执行计划如下图:

经过查询缓存池中各个表的具体缓存状况发现,表ALARM_TO_TRAPH上的索引ALARM_TO_TRAPH_INDEX1与索引TRAPH_CUID均已被缓存,而在该执行计划中需要做全表扫描操作的CURRENT_ALARM则只有部分块被缓存,充分说明了引起物理读需求的正是对CURRENT_ALARM的全表扫描操作,为了进一步证实这一点,我们做了一次10046事件的trace,该事件可以记录SQL语句执行过程中详细的等待事件以及相关信息,在trace中发现大量db file sequential read等待时间,摘录如下:

以上记录中p1,p2对应了数据文件顺序读具体发生的文件号和块号,经过查询确定以上相关文件号和数据块号具对应于表CURRENT_ALARM,至此本次性能问题的主要原因已十分明确,即CURRENT_ALARM表未被完全缓存,引起问题的可能有两种:1.表中存在大量的chained rows即链式行;2.数据库高速缓存在系统高峰时段存在瓶颈。

对于前一种可能性,我们首先分析了表的结构,CURRENT_ALARM表包括96个列,其中包括大量varchar2(255)类型的长列,理论单行长度可能达到14K字节,的确可能引起严重的链式行,为了进一步证实,我们对该表做了一次链式行扫描,操作如下:

ANALYZE TABLE CURRENT_ALARM  LIST CHAINED ROWS INTO chained_rows;

该分析操作会将表上的链式行记录到临时表chained_rows中,查询chained_rows发现没有记录,说明表CURRENT_ALARM上没有链式行的问题。其后我们通过函数计算CURRENT_ALARM表上的行长度,发现最长的一行占用1367个字节,就目前来说仍不至于发生严重的链式行问题。

经过以上分析问题已经定位到了高峰时段数据库高速缓存的使用状况上了,通过在早晨,上班高峰时段以及下班时段的进一步观察,我们发现在早晨上班之前,buffer_cache中大约有600个free块可以立即使用,在该时段做上述查询不存在物理读的操作,而在上班高峰时段free块的数量下降到个位数乃至为零,即需要加载数据块时没有可以立即使用的空闲块,同时因为其他查询更为活跃,CURRNET_ALARM表上的数据块在读入后迅速被刷出,没有被缓存住以便于下次的查询,导致了性能问题的最终产生。

总结

由于数据库高速缓存在高峰时段没有空闲块导致需要全表扫描的表未被完全缓存,进而在多用户同时查询的情况下产生大量的物理读等待,影响了正常业务的运行。针对该问题提出以下建议:

  • 增大SGA总量,增大数据库高速缓存即DB_CACHE_SIZE的空间,并扩展高速缓存的keep池,将表CURRENT_ALARM保存在keep池中,保证不被刷出。
  • 定期分析CURRENT_ALARM表的行扩展情况,从而避免可能的链式行的产生。
  • 在可能的情况下,为上述查询语句加上执行计划暗示即ALL_ROWS的hit,使其在连接方式上倾向于使用哈希连接,可以降低三分之一的逻辑读。
  • 增大数据库使用内存总量的同时可能引发CPU的使用量有一定上升,需要密切关注主机的负载情况。

[gview file=”http://askmac.cn/wp-content/uploads/resource/BUFFPOOL.pdf”]

绑定变量介绍

Oracle在执行SQL语句时,普遍存在以下几个步骤:

  1. 当SQL语句首次执行,Oracle将确认该句语句的语法是否正确(语法解析Syntax parse)并进一步确认语句相关表和列的存在性等因素(语义解析semantic parse)以及优化器决定执行计划等步骤。整个过程称之为硬解析,硬解析消耗大量的CPU时间和系统资源。硬解析过多会有效降低系统性能。
  1. 若之前已进行过硬解析,且解析后的分析树和执行计划仍存在于共享池中,则同样的SQL仅需要软解析。软解析将输入的SQL语句转换为哈希代码,同共享池内哈希链表上的已有记录进行对比,找出对应的游标信息,使用已有的执行计划执行。
  1. 绑定变量,将实际的变量值代入SQL语句中。
  1. 执行SQL语句,查询语句将返回结果集。

不使用绑定变量的SQL语句,Oracle无法将它们视为相同的,如以下两句语句:

select * from emp where empno=1234

select * from emp where empno=5678

因为自由变量的不同,Oracle认为以上是2句不同的语句,则当第一条被硬解析后,第二条SQL执行时仍无法避免硬解析。实际在以上不使用绑定变量的情况中,只要自由变量有所改变则需要一次硬解析。这是强烈建议使用绑定变量的主要原因,使用绑定变量的语句变量的实际值仅在SQL执行的最后阶段被代入。如以下语句:

select * from emp where empno=:x

该语句使用绑定值:x替代自由变量,在应用中语句可能以预编译或普通编译的方式存在,仅在执行阶段代入变量值,多次执行仅需要一次硬解析,较不使用绑定变量情况性能大大提升。

同时过多的硬解析还会引发共享池碎片过多的问题。因为每当需要硬解析一个SQL或者PLSQL语句时,都需要从shared pool中分配一块连续的空闲空间来存放解析结果。Oracle首先扫描shared pool查找空闲内存,如果没有发现大小正好合适的空闲chunk,就查找更大的chunk,如果找到比请求的大小更大的空闲chunk,则将它分裂,多余部分继续放到空闲列表中。因为过多的硬解析加剧了内存段分配的需求,这样就产生了碎片问题。系统经过长时间运行后,就会产生大量小的内存碎片。当请求分配一个较大的内存块时,尽管shared pool总空闲空间还很大,但是没有一个单独的连续空闲块能满足需要。这时,就可能产生 ORA-4031错误。

通常我们可以通过以下SQL语句将系统中非绑定变量的语句找出:

SELECT substr(sql_text,1,40) “SQL”,

count(*) ,

sum(executions) “TotExecs”

FROM v$sqlarea

WHERE executions < 5 –-语句执行次数

GROUP BY substr(sql_text,1,40)

HAVING count(*) > 30 –-所有未共享的语句的总的执行次数

ORDER BY 2;

以上语句在实际使用中substr函数截取到的字符串长度需要视乎实际情况予以变化。

对于非绑定变量且短期内无法修改的应用,Oracle存在参数cursor_sharing可以改善其表现。cursor_sharing默认为exact,对使用自由变量的语句不做额外处理;当设为force时,非绑定变量的SQL语句被进一步处理以达到共享SQL的目的,但以上处理步骤同样要消耗一定的CPU时间;当设为similar时,若数据库存在语句相关统计信息则其表现如exact,若无统计信息则表现为force。cursor_sharing参数是Oracle针对无法修改的非绑定变量应用所提出的折中方案,但cursor_sharing为force值时存在一定SQL引发bug或语句无效的情况,且额外的处理操作同样需要消耗一定量的CPU时间和系统资源。故针对系统性能的最优方案往往是直接修改应用代码,使用绑定变量特性。

UNDO表空间监控说明

在Oracle 10g版本中可以使用V$UNDOSTAT视图用于监控实例中当前事务使用UNDO表空间的情况。视图中的每行列出了每隔十分钟从实例中收集到的统计信息。每行都表示了在过去7*24小时里每隔十分钟UNDO表空间的使用情况,事务量和查询长度等信息的统计快照。

UNDO表空间的使用情况会因事务量变化而变化,一般我们在计算时同时参考UNDO表空间的平均使用情况和峰值使用情况。

以下SQL语句用于计算过去7*24小时中UNDO表空间的平均使用量:

select ur undo_retention,

dbs db_block_size,

((ur * (ups * dbs)) + (dbs * 24)) / 1024 / 1024 as “M_bytes”

from (select value as ur from v$parameter where name = ‘undo_retention’),

(select (sum(undoblks) / sum(((end_time – begin_time) * 86400))) ups

from v$undostat),

(select value as dbs from v$parameter where name = ‘db_block_size’)

以下SQL语句则按峰值情况计算UNDO表空间所需空间:

select ur undo_retention,

dbs db_block_size,

((ur * (ups * dbs)) + (dbs * 24)) / 1024 / 1024 as “M_bytes”

from (select value as ur from v$parameter where name = ‘undo_retention’),

(select (undoblks / ((end_time – begin_time) * 86400)) ups

from v$undostat

where undoblks in (select max(undoblks) from v$undostat)),

(select value as dbs from v$parameter where name = ‘db_block_size’)

需要注意因RAC情况下一般存在2个UNDO表空间,视乎实际情况分别在2个实例中执行以上查询。

一般来说为了尽可能维护日常业务的正常运行,我们建议按照峰值情况估算和分配UNDO表空间的大小,虽然这样存在存储空间上的浪费,但是可以避免UNDO表空间不足所带来的问题。

同时我们也可以使用DBA_UNDO_EXTENTS视图实时监控UNDO表空间的使用情况:

select sum(bytes / 1024 / 1024), status, tablespace_name

from dba_undo_extents

group by status, tablespace_name;

该查询将返回以STATUS分组的各状态回滚信息所使用的空间量,一般存在三种STATUS状态:EXPIRED,UNEXPIRED,ACTIVE。ACTIVE表示目前仍活跃的事务相关回滚信息,UNEXPIRED表示虽然事务已经结束但回滚信息保留的时间仍未超过实例参数UNDO_RETENTION所设定的值,EXPIRED表示回滚信息保留时间已超过UNDO_RETENTION所设定的值。

在UNDO表空间未启用guarantee选项的情况下(当前使用情况),新事务的回滚空间分配遵循以下依据:

a)         寻找不存在ACTIVE区间的回滚段,若没有则创建一个新的回滚段,若空间不允许生成新段,则返回错误。

b)        如果有一个回滚段被选中,但是其中空闲的空间并不足以存储该事务的回滚信息,那么它将尝试创建区间,如果表空间上没有空间,那么将会进入下一步。

c)         如果创建新区间失败,它将会搜索其他回滚段中的EXPIRED区间并重用。

d)        如果其他回滚段中没有EXPIRED区间可使用,那么它会继续搜索其他回滚段中UNEXPIRED区间并重用,注意事务不会重用本回滚段中的UNEXPIRED区间,故UNEXPIRED的回滚空间仅部分可以为Oracle重用;若仍得不到所需则返回错误。

当我们观察到ACTIVE回滚信息所占用空间很大时,说明系统目前运行的事务繁忙。因目前未启用UNDO表空间的guarantee选项,故EXPIRED的全部回滚空间与UNEXPIRED的部分回滚空间可以为Oracle复用,在实时监控时主要观察ACTIVE状态回滚信息使用的空间即可。

在系统相关业务不变的情况下,我们通过计算UNDO表空间的峰值使用情况即可最大程度完善UNDO表空间的配置;而当系统处于业务调整阶段,如新的业务加入或业务时段调整情况下,则需要进一步实时监控UNDO表空间使用情况,以满足动态调整需求。

沪ICP备14014813号-2

沪公网安备 31010802001379号