Expert One-on-One Oracle阅读笔记
Expert
One-on-One Oracle阅读笔记
第 6
章 数据库表
6.1 表的类型
1. 堆组织表
2. 索引组织表
3. 聚簇表
4. 散列聚簇表
5. 嵌套表
6. 临时表
7. 对象表
一张表最多有1000列;表的行数理论上没有限制;表上索引个数可以是列的全排列数,而且一次性能够使用32个;表的数量没有限制。
6.2 术语
高水位标记 High Water Mark
曾经包含数据的最右边的块。在全表扫描时,Oracle将扫描高水标记一下的所有块,即使它们不含数据。TRUNCATE将重新设置高水标记。
自由列表
Freelist
在Oracle中用来跟踪高水标记以下有空闲空间的块对象。保留在高水标记以上的块,只有Freelist为空时才能被用到。
并行更新数据时,配置多个Freelist能提高整体性能,代价是增加了存储空间。
PCTFREE和PCTUSED
INITIAL, NEXT和PCTINCREASE
建议使用Local
Managed表空间并设置Extents大小相等。而在没有使用Local
Managed表空间的情况下,建议总是设置INITIAL=NEXT和PCTINCREASE=0,以模拟Local
Managed表空间的使用。
MINEXTENTS和MAXEXTENTS
LOGGING和NOLOGGING
INITRANS和MAXTRANS
堆组织表
6.3 索引组织表
数据在IOT中根据主键存储和排序。IOT特别适用于IR(信息检索)、空间和OLAP应用程序。
IOT名义上是表,但它们的段实际上是索引段。要显示空间使用等就要先把IOT表的名字转换成潜在的索引名。默认值是SYS_IOT_TOP_<object_id>,object_id是为表分配的内部对象ID。推荐在建表时指定索引名。
主要应用
对只包含主键列的表:使用堆组织表将有100%多的额外开销;
1. 构建自己的索引结构:例如自己实现一个提供大小写不敏感查询的类似函数索引
CREATE
TABLE emp AS SELECT * FORM scott.emp;
CREATE
TABLE upper_name
(x$ename,x$rid,
PRIMARY
KEY(x$ename,x$rid)
)
ORGANIZATION INDEX
AS
SELECT UPPER(ename),ROWID FROM
emp;
CREATE
OR REPLACE TRIGGER upper_ename
AFTER
INSERT OR UPDATE OR DELETE ON emp
FOR EACH
ROW
BEGIN
IF (UPDATING AND
(:OLD.ename||'x'<>:NEW.ename||'x'))
THEN
DELETE FROM
upper_name
WHERE
x$ename=UPPER(:OLD.ename)
AND
x$rid=:OLD.rowid;
INSERT INTO upper_ename(x$ename,x$rid)
VALUES (UPPER(:NEW.ename),:NEW.rowid);
ELSIF
(INSERTING)
THEN
INSERT INTO upper_ename(x$ename,x$rid)
VALUES (UPPER(:NEW.ename),:NEW.rowid);
ELSIF
(DELETING)
THEN
DELETE FROM
upper_name
WHERE
x$ename=UPPER(:OLD.ename)
AND
x$rid=:OLD.rowid;
END IF;
END;
2. 需要加强数据的共同定位或希望数据按特定的顺序物理存储时
对应Sybase和SQL Server用户,这种情况会采用聚簇索引,而这可能达到110%的额外开销,而IOT没有。经常用BETWEEN对主键或者唯一键进行查询,则会降低I/O数量。
主要选项
NOCOMPRESS/COMPRESS N
压缩N列,即对其中前N列相同的值进行压缩。从而能够允许更多数据进入Buffer Cache,代价是略多的CPU能量。
OVERFLOW
PCTTHRESHOLD N/INCLUDING column_name
索引段的存储要密集于普通数据段(每块的行数要多),一般PCTUSED是没有意义的。而OVERFLOW子句允许设置另一个段以允许IOT中的行数据太大时溢出的这个段中。它再次引入PCTUSED,这样PCTUSED和PCTFREE对OVERFLOW段有对于堆组织表中相同的含义。而使用方法是如下中的一种:
PCTTHRESHOLD——当行中数据超出此百分比,该行尾部的列溢出到溢出块;
INCLUDING——指定列之前的列均存入索引块,之后的列存入溢出块。
二次索引
只要主键是IOT,可以在索引中拥有索引。但不像其他一般索引,它不包含真正rowid(物理地址),而是基于主键IOT的逻辑rowid,作用稍小。对于IOT的二次索引访问实际有两个扫描执行(一般表只需一个扫描索引结构),一个在二次结构中,一个在IOT本身中。
6.4 索引聚簇表
Oracle中聚簇是存储一组表的方法,而不是如同SQL
Server、Sybase中那样(那是Oracle中的IOT)。概念上是通过聚簇码列将几张表“预连接”,尽可能将聚簇码列相同的几张表的行放入同一个块中。
CREATE CLUSTER
emp_dept_cluster
(deptno
NUMBER(2))
SIZE 1024;
CREATE INDEX
emp_dept_cluster_idx
ON CLUSTER
emp_dept_cluster;
CREATE TABLE dept
(deptno NUMBER(2) PRIMARY
KEY,
dname
VARCHAR2(14),
loc
VARCHAR2(3)
)
CLUSTER
emp_dept_cluster(deptno);
CREATE TABLE emp
(empno NUMBER PRIMARY
KEY,
ename
VARCHAR2(10),
...
deptno
NUMBER(2) REFERENCES dept(deptno)
)
CLUSTER
emp_dept_cluster(deptno);
BEGIN
FOR x
IN(SELECT * FROM scott.dept)
LOOP
INSERT
INTO dept VALUES(x.deptno,x.dname,x.loc);
INSERT
INTO emp
SELECT *
FROM scott.emp
WHERE
deptno=x.deptno;
END
LOOP;
END;
注意这里的插入方法,这将尽可能保证每个块中放置尽可能多的聚簇码值,并让可以“预连接”的两个表中的值尽可能在同一个块中。
DBMS_ROWID.ROWID_BLOCK_NUMBER(rowid)可用于检查rowid所属块。
很容易发现dept和emp有重复的rowid,表和rowid可以唯一确定行,rowid伪列只有在一张表中才是唯一的!
不使用聚簇的情况:
1.聚簇可能消极影响DML性能;
2.全扫描表的性能会受到影响——不仅仅扫描一个表,而是对多个表全扫描;
3.聚簇中的表不能TRUNCATE。
6.5 散列聚簇表
概念类似索引聚簇表,但用散列函数代替了聚簇码索引。Oracle采用行的码值,使用内部函数或者自定义的函数进行散列运算,从而指定数据的存放位置。这样没有在表中增加传统的索引,因此不能Range
Scan散列聚簇中的表,而只能全表扫描(除非单独建立索引)。
CREATE CLUSTER
hash_cluster
(hash_key NUMBER)
HASHKEYS 1000
SIZE 8192;
索引聚簇需要空间时是动态分配,而散列聚簇表在创建时确定了散列码数(HASHKEY)。Oracle采用第一个不小于HASHKEY的质数作为散列码数,将散列码数*SIZE就得到分配的空间(字节),可容纳HASHKEYS/TRUNC(BLOCKSIZE/SIZE)字节的数据。
性能上,散列聚簇表消耗较少I/O,较多CPU,所需执行时间较少,大体取决于CPU时间(当然可能要等待I/O,取决于配置)。
下列情况下使用散列聚簇表较为合适:
1. 在一定程度上精确知道整个过程中表中记录行数或者合理的上限,以确定散列码数;
2. 不大量执行DML,尤其是插入。更新不会产生显著的额外开销,除非更新HASHKEY,这样会导致行迁移;
3. 总是通过HASHKEY值访问数据。
6.6 嵌套表
两种使用嵌套表的方法:
1. PL/SQL代码中作为扩展PL/SQL语言;
2. 作为物理存储机制,以持久地存储集合。
嵌套表语法
创建嵌套表类型:
CREATE TABLE dept
(deptno
NUMBER(2) PRIMARY KEY,
dname
VARCHAR2(14),
loc VARCHAR2(13)
);
CREATE TABLE emp
(empno
NUMBER(4) PRIMARY KEY,
ename
VARCHAR2(10),
job
VARCHAR2(9),
mgr NUMBER(4) REFERENCES
emp,
hiredate DATE,
sal NUMBER(7,
2),
comm NUMBER(7,
2),
deptno NUMBER(2) REFERENCES
dept
);
INSERT INTO dept SELECT * FROM
scott.dept;
INSERT INTO emp SELECT * FROM
scott.emp;
CREATE OR REPLACE TYPE
emp_type
AS OBJECT
(empno
NUMBER(4),
ename
VARCHAR2(10),
job
VARCHAR2(9),
mgr
NUMBER(4),
hiredate DATE,
sal NUMBER(7,
2),
comm NUMBER(7,
2)
);
CREATE OR REPLACE TYPE
emp_tab_type
AS TABLE OF
emp_type;
使用嵌套表:
CREATE TABLE
dept_and_emp
(deptno
NUMBER(2) PRIMARY KEY,
dname
VARCHAR2(14),
loc
VARCHAR2(13),
emps
emp_tab_type
)
NESTED TABLE emps STORE AS
emps_nt;
可以在嵌套表上增加约束:
ALTER TABLE emps_nt ADD CONSTRAINT emps_empno_unique
UNIQUE(empno) ;
嵌套表不支持参照完整性约束,不能参考任何其他表甚至自己:
ALTER TABLE emps_nt ADD CONSTRAINT
mgr_fk
FOREIGN KEY(mgr) REFERENCES
emps_nt(empno);
会产生错误ORA-30730。
INSERT INTO
dept_and_emp
SELECT dept.*,
CAST(
MULTISET( SELECT empno, ename, job, mgr, hiredate, sal,
comm
FROM
emp
WHERE emp.deptno
= dept.deptno ) AS emp_tab_type )
FROM
dept;
MULTISET用来告诉Oracle子查询返回不止一行,CAST用来告诉Oracle将返回设置为一个集合类型。
查询时,嵌套表中的数据将在同一列中:
SELECT deptno, dname, loc, d.emps AS
employees
FROM dept_and_emp
d
WHERE deptno =
10;
Oracle同样提供方法去掉集合的嵌套,像关系型表一样处理(能够将EMPS列当作一个表,并自然连接且不需要连接条件):
SELECT d.deptno, d.dname,
emp.*
FROM dept_and_emp D, TABLE(d.emps)
emp;
按照“每行实际是一张表”的思想来更新:
UPDATE
TABLE(
SELECT emps
FROM
dept_and_emp
WHERE
deptno = 10
)
SET comm = 100;
但如果返回SELECT emps
FROM dept_and_emp WHERE deptno = 10少于一行,更新将失败(普通情况下更新0行是许可的),并返回ORA-22908错误——如同更新语句没有写表名一样;如果返回多于一行,更新也会失败,返回ORA-01427错误。这说明Oracle在使用了嵌套表后认为每一行指向另一个表,而不是如同关系型模型那样认为是另一个行集。
插入与删除的语法:
INSERT INTO TABLE
(SELECT emps FROM dept_and_emps WHERE
deptno=10)
VALUES
(1234,'NewEmp','Clerk',7782,SYSDATE,1200,NULL);
DELETE FROM TABLE
(SELECT emps FROM dept_and_emps WHERE
deptno=20)
WHERE
ename='SCOTT';
一般而言,必须总是连接,而不能单独查询嵌套表(如EMPS)中的数据,但是如果确实需要,是可以的。提示NESTED_TABLE_GET_REFS被用于EXP和IMP处理嵌套表。
SELECT
/*+NESTED_TABLE_GET_REFS+*/
NESTED_TABLE_ID, SYS_NC_ROWINFO$
FROM
"TKYTE"."EMPS_NT";
而我们察看EMPS_NT的表结构是看不到NESTED_TABLE_ID,SYS_NC_ROWINFO$两列的。对父表DEPT_AND_EMP来说NESTED_TABLE_ID是一个外键。
使用这个提示就可以直接操作嵌套表了:
UPDATE
/*+NESTED_TABLE_GET_REFS+*/ emps_nt
SET
ename=INITCAP(ename);
嵌套表存储
上例中,现实产生了两张表:
DEPT_AND_EMP
deptno
NUMBER(2)
dname
VARCHAR2(14)
loc
VARCHAR2(13)
SYS_NC0000400005$
RAW(16)
EMPS_NT
SYS_NC_ROWINFO$
NESTED_TABLE_ID
RAW(16)
empno
NUMBER(4)
ename
VARCHAR2(10)
job
VARCHAR2(9)
mgr
NUMBER(4)
hiredate
DATE
sal
NUMBER(7,2)
comm
NUMBER(7,2)
默认情况下,每个嵌套表列都产生一个额外的RAW(16)隐藏列,并在其上创建了唯一约束,用以指向嵌套表。而嵌套表中有两个隐藏列:SYS_NC_ROWINFO$是作为一个对象返回所有标量元素的一个伪列;另一个NESTED_TABLE_ID的外键回指向父表。
可以看到真实代码:
CREATE TABLE
TKYTE.DEPT_AND_EMP
(DEPTNO
NUMBER(2,0),
DNAME
VARCHAR2(14),
LOC
VARCHAR2(13),
EMPS
EMP_TAB_TYPE)
PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255
LOGGING
STORAGE(INITIAL 131072 NEXT
131072
MINEXTENTS 1 MAXEXTENTS 4096
PCTINCREASE 0 FREELISTS 1 FREELIST GROUP
1
BUFFER_POOL DEFAULT)
TABLESPACE USER
NESTED TABLE EMPS
STORE AS EMPS_NT
RETURN BY VALUE;
RETURN BY
VALUE用来描述嵌套表如何返回到客户应用程序中。
NESTED_TABLE_ID列必须是索引的,那么较好的解决办法就是使用IOT存储嵌套表。
CREATE TABLE
TKYTE.DEPT_AND_EMP
(DEPTNO
NUMBER(2,0),
DNAME
VARCHAR2(14),
LOC
VARCHAR2(13),
EMPS
EMP_TAB_TYPE)
PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255
LOGGING
STORAGE(INITIAL 131072 NEXT
131072
MINEXTENTS 1 MAXEXTENTS 4096
PCTINCREASE 0 FREELISTS 1 FREELIST GROUP
1
BUFFER_POOL DEFAULT)
TABLESPACE USER
NESTED TABLE EMPS
STORE AS EMPS_NT
((empno NOT NULL,
UNIQUE(empno),
PRIMARY
KEY(nested_table_id,empno))
ORGANIZATION
INDEX COMPRESS 1)
RETURN BY VALUE;
这样与最初默认的嵌套表相比,使用了较少的存储空间并有最需要的索引。
不使用嵌套表作为永久存储机制的原因
1.增加了RAW(16)列的额外开销,父表和子表都将增加这个额外的列;
2.当通常已经有唯一约束时,父表上的唯一约束是额外开销;
3.没有使用不支持的结构(NESTED_TABLE_GET_REFS),嵌套表不容易使用。
一般推荐在编程结构和视图中使用嵌套表。如果要使用嵌套表作为存储机制,确保嵌套表是IOT,以避免NESTED_TABLE_ID和嵌套表本身中索引的额外开销。
6.7 临时表
Oracle的临时表与其他数据库中的不同,其定义是“静态”的。以事务(ON COMMIT
DELETE ROWS)或者会话(ON COMMIT
PRESERVE ROWS)为基础,只是说明数据的生命期,而在数据库中创建临时表一次,其结构总是有效的,被作为对象存在数据字典中了,这样也就允许对临时表建立视图、存储过程中用静态SQL引用临时表等等。
在实际开发中,考虑到DDL是消耗较大的操作,应该避免在运行时操作,而是将应用程序需要的临时表在程序安装时就创建,而只是在存储过程中简单的INSERT、SELECT。
临时表不支持的永久表的特性有:
1. 不能用参照完整性约束,也不能被参照完整性约束所引用;
2. 不能有VARRAY或者NESTED
TABLE类型的列;
3. 不能是IOT;
4. 不能是索引或者散列聚簇;
5. 不能分区;
6. 通过ANALYZE命令不能产生统计信息,也即是说优化器在临时表上没有真正的统计功能。
由于缺少统计功能,那么CBO(基于成本的优化器)的性能将受到极大的影响,因此应当尽可能使用INLINE
VIEW。
要让临时表拥有正确的统计信息,CBO产生正确的决策,可以先建立一张结构与临时表完全相同的普通表:
CREATE TABLE
temp_all_objects
AS
SELECT * FROM all_objects WHERE
1=0;
CREATE INDEX temp_all_objects_idx ON
temp_all_objects(object_id);
选择插入代表性数据后进行分析:
...
ANALYZE TABLE temp_all_objects COMPUTE STATISTICS FOR
ALL INDEX;
BEGIN
DBMS_STATS.CREATE_STAT_TABLE(ownname =>
USER,
stattab =>
'STATS');
DBMS_STATS.EXPORT_TABLE_STATS(ownname =>
USER,
tabname
=> 'TEMP_ALL_OBJECTS',
stattab
=> 'STATS');
DBMS_STATS.EXPORT_INDEX_STATS(ownname =>
USER,
tabname
=> 'TEMP_ALL_OBJECTS_IDX',
stattab
=> 'STATS');
END;
建立临时表:
DROP TABLE
temp_all_objects;
CREATE GLOBAL TEMPORARY TABLE
temp_all_objects
AS
SELECT * FROM all_objects WHERE
1=0;
导入正确的信息后CBO将使用这些信息决定执行模式:
CREATE INDEX temp_all_objects_idx ON
temp_all_objects(object_id);
BEGIN
DBMS_STATS.IMPORT_TABLE_STATS(ownname =>
USER,
tabname
=> 'TEMP_ALL_OBJECTS',
stattab
=> 'STATS');
DBMS_STATS.IMPORT_INDEX_STATS(ownname =>
USER,
tabname =>
'TEMP_ALL_OBJECTS_IDX',
stattab
=> 'STATS');
END;
6.8 对象表
基于类型(Type)创建的表,而不是作为列的集合。创建语法:
CREATE TABLE t OF
some_type;
对于下例:
CREATE OR REPLACE TYPE
address_type
AS OBJECT
(city
VARCHAR2(30),
street
VARCHAR2(30),
state
VARCHAR2(2),
zip
NUMBER
);
CREATE OR REPLACE TYPE
person_type
AS OBJECT
(name
VARCHAR2(30),
dob
DATE,
home_address
address_type,
work_address
address_type
);
CREATE TABLE people OF
person_type;
通过执行如下语句,可以看到数据库中实际存放的结构:
SELECT
name,segcollength
FROM SYS.COL$
WHERE obj#=(SELECT
object_id
FROM user_objects
WHERE
object_name='PEOPLE');
PEOPLE
SYS_NC_OID$
16
SYS_NC_ROWINFO$
1
NAME
30
DOB
7
HOME_ADDRESS
1
SYS_NC00006$
30
SYS_NC00007$
30
SYS_NC00008$
2
SYS_NC00009$
22
WORK_ADDRESS
1
SYS_NC00011$
30
SYS_NC00012$
30
SYS_NC00013$
2
SYS_NC00014$
22
SYS_NC_OID$是系统为表产生的Object
ID,RAW(16),其上有唯一性索引。它是一主键为基础,并不是系统产生的,是一个伪列,且没有在硬盘上真正消耗空间;
SYS_NC_ROWINFO$类似于嵌套表中,可作为单独一列返回整行;
NAME,
DOB是表中原有标量;
HOME_ADDRESS,
WORK_ADDRESS可作为单个对象,返回所代表的列的集合;
SYS_NCnnnnn$是内嵌对象类型的标量实现。
第 7
章 索引
7.1 索引类别
B*树索引——传统索引,从码转向行
索引组织表
B*树聚簇索引——从聚簇码转向包含与行相关的聚簇码的块
反向码索引——更均匀的分配索引条目
降序索引——允许数据在索引中降序排列
位图索引
基于函数的索引
应用程序域索引
interMedia文本索引
7.2 B*树索引
索引中的叶结点实际上是双向链表,这样不必经过上级结点可以直接Range
Scan。在B*树中实际不会出现不唯一的索引,对不唯一索引只要加上ROWID就唯一了。对于排序,不唯一索引先根据索引值排序,然后根据ROWID排序;唯一索引根据索引值排序。
B*树一般2-3层,且自动平衡。
反向码索引
实际就是将索引值的内部表示(高低位)反转,这样相邻的索引值之间距离变远,便于并发。缺点是不能支持所有正常索引的功能,例如无法支持WHERE
x>5这样的谓词。
降序索引
使用环境
1. 处理表中很多行,但索引就能替代表;
如对一张已经建立了索引的表COUNT(*)
2. 访问表中极少的行,一般2-3%(如果表中有多列或较宽的列,则此百分比可升至20-25%)。
否则效率不及全表扫描。原因在于由索引访问块,则访问顺序几乎随机,每个块可能要访问多次,则不及全表的块一次性扫描效率高。但这同样需要考虑表中特定的数据,若数据在表中基本按主键顺序排列,则使用索引又会效率很高——每个块不会或很少访问多次。
视图USER_INDEXES中CLUSTERING_FACTOR列说明了数据存放的随机程度:
若CLUSTERING_FACTOR接近块的数量,则表较易排序,单个叶块上的索引条目趋向于指向同一个块上的列;
若CLUSTERING_FACTOR接近行数,则表是随机排序的。
7.3 位图索引
在7.3版本中加入,而8i标准版不支持。为数据仓库等特定查询环境设计,不应用在OLTP系统或许多并行会话经常更新数据的系统。使用的另一个基本原则是基数(字段不同的取值)较低。总体上适合集中读取的环境,而极不适合集中写入的环境。原因在于任何需要更新同一个位图索引条目的修改都将锁定整个位图,严重抑制了并发性。通常建立位图索引的时间短过B*树索引。
7.4 基于函数的索引
在8.1.5版加入,8i标准版不支持。
实现前提
1. 在自己的模式中的表上创建基于函数的索引,必须具有系统特权QUERY
REWRITE;对其他用户,必须具有系统特权GLOBAL
QUERY REWRITE;
2. 使用CBO;
3. 必须设置如下会话或系统变量:
QUERY_REWRITE_ENABALED=TRUE
QUERY_REWRITE_INTEGRITY=TRUSTED
这些参数可以通过ALTER
SESSION或者ALTER
SYSTEM来修改,或者直接修改init.ora。其中QUERY_REWRITE_INTEGRITY=TRUSTED说明系统可以信任函数,不会产生二义性结果;
4. 使用SUBSTR来限定从用户定义的函数中返回的VARCHAR2或RAW类型的值。
注意
索引项的大小应在数据块的1/3以内,对于普通8K而言就是3218字节(否则会报ORA-01450错误代码)。因此,对于返回VARCHAR2或RAW类型值的函数应用SUBSTR来限制。为了掩藏其复杂性并提高灵活性(允许以后更改SUBSTR的大小),我们可以使用视图来掩盖(即将相应字段更换为SUBSTR后的值),系统同样会识别出基于函数的索引。
对于8.1.7之前的系统要注意,使用to_date作为建立索引的函数时会报ORA-01743错误代码,解决方法是自己实现一个to_date的外壳。
对于用户自定义的函数建立索引后,不能Direct
Path导入,而对系统提供的函数不受影响。
鉴于每次插入或更新,对应函数都执行一次,其修改的效率降低了很多,但带来很多查询的效率提高。
7.5 应用程序域索引
又称为“可扩展的索引”,允许创建自己的索引结构,如同系统提供的一样工作。
7.6 常见问题解答
索引能否在视图中使用
系统是用视图的定义来访问数据,较好的索引基表,就能够提高视图效率。
索引和NULL
除了B*树索引聚簇的特殊情况,B*树索引不存储NULL的条目,而位图索引和聚簇索引存储所有NULL条目。
利用这一特性,若表中某列大部分取值一致,则可将其修改为NULL,将极大的缩小索引占用空间。
外键上的索引
非索引的外键是导致死锁的主要原因。父表修改时将全表锁定子表,若关联着的子表外键无索引,则每次删除父表中的一行就会对子表全表扫描一次。
不需要外键索引的情况:
不从父表中删除;
不更新父表主键或唯一键的值;
不连接父表和子表。
未用到索引的原因
1. 一张T(x,y)上有索引的表T,查询SELECT *
FROM T WHERE y=5,由于谓词未包含X,则必须全扫描索引条目,则优化器通常选择对T全表扫描;查询SELECT x,y
FROM T WHERE y=5,则优化器会注意到为得到x和y,不必进入表,且一般索引小于表,则CBO会选择快速全扫描索引。
2. 查询SELECT
COUNT(*) FROM T,表上有一个B*树索引,则考虑到索引可能在一系列包含空值的列上,优化器选择全扫描表。
3. 对列使用了函数,而索引只是基于列的。
4. 一个字符列上有索引,但谓词是indexed_column=5,这里系统隐含使用了to_number函数,同3,不会使用索引。
5. 使用索引实际会降低速度。
6. 很长时间没有分析表了,表的增长较快,这样CBO会作出错误的判断。
索引中空间重用
只要出现的行可重用,索引块上的空间就能重用;
当一个索引块为空时将加入FREELIST,从而可以重用,但和堆组织表不同,即便只有一个索引,也会占据一个块。
第 8
章 导入和导出
8.1 IMP/EXP的工作原理
大量导出
EXP和一般文件一样,在支持搜索的设备(即文件系统?)上能产生的文件大小是有限制的,它使用一般OS文件的API,在32位系统中限制的文件大小为2GB。已知以下4种解决方法。
1. 使用FILESIZE参数
该参数在8i中引入。设置后将限制每个导出的DMP文件的大小,问题在于必须大致估计文件个数(即总导出量),并提供FILE参数列表,否则将产生交互式会话,请求提供文件名,而对于定时无人值守的操作,长时间无响应会产生错误。另一方面,将多个DMP文件导入时可以一次性提供多个文件名,即便实际文件不存在,只会提示警告而不会出错。
2. 导出较小的数据文件
比如按方案导出等
3. 导出到OS管道
此方法目前仅应用于UNIX。
#!/bin/csh -f
# Set this to the userid you want to perform the
export as I always use OPS$ (os
# authenticated) accounts for all jobs that will be
run in the background. In that
# way a password never appears in a script file or in
the ps output.
setenv UID /
# This is the name of the export file. SPLIT will use this to
name the pieces of
# the compressed DMP
file.
setenv FN
exp.`date +%j_%Y`.dmp
# This is the name of the named pipe we will
use.
setenv PIPE
/tmp/exp_tmp_ora8i.dmp
# Here I limit the size of the compressed files to
500 MB each. Anything less
# than 2 GB would be
fine.
setenv MAXSIZE 500m
# This is what we are going to export. By default I
am doing a full database
# export.
setenv EXPORT_WHAT "full=y
COMPRESS=n"
# This is where the export will go
to.
cd
/nfs/atc-netapp1/expbkup_ora8i
# Clear out the last
export.
rm expbkup.log export.test exp.*.dmp* $PIPE
# Create the named
pipe.
mknod $PIPE p
# Write the datetime to the log
file.
date >
expbkup.log
# Start a gzip process in the background. Gzip will
read the pipe and put the
# compressed data out to split. Split will then create 500 MB files out of
the
# input data adding .aa, .ab, .ac, .ad, ... file
extensions to the template name
# found in $FN.
( gzip < $PIPE ) | split -b $MAXSIZE - $FN.
&
# Now, start up export. The Gzip above is waiting for
export to start filling the
# pipe up.
exp userid=$UID buffer=20000000 file=$PIPE
$EXPORT_WHAT >>& expbkup.log
date >>
expbkup.log
# Now the export is done, this is how to IMP. We need
to sort the filenames and
# then simply cat their contents into gunzip. We
write that into the pipe. IMP
# will then read that pipe and write what it would do
to stderr. The >>& in
the
# csh redirects both stdout and stderr for
us.
date >
export.test
cat `echo $FN.* | sort` | gunzip > $PIPE
&
imp userid=$UID file=$PIPE show=y full=y
>>& export.test
date >>
export.test
# Clean up the pipe, we don't need it
anymore.
rm -f $PIPE
4. 导出到一个不支持搜索的设备
仅用于UNIX。指定设备名,可以直接将数据导出到磁带设备中。
数据子集
即设置QUERY参数。但条件中往往含有各个OS上的保留字符,那么用par(参数)文件的形式更通用一点。如:
Windows下:
C:\exp>exp userid=… tables=t query=”””where
object_id<5000”””
UNIX下:
$exp userid=… tables=t query=\ ”where
object_id\<5000\”
使用参数文件:
exp.par: query=”where object_id<5000”
exp userid=… tables=t
parfile=exp.par
数据传输
即直接将一个数据库上的数据文件附加到另一个数据库中。
规则:
1. 源数据库与目标数据库必须运行在相同的硬件平台上;
2. 源数据库与目标数据库必须使用相同的字符集;
3. 源数据库一定不能有与目标表空间同名的表空间;
4. 源数据库与目标数据库的块大小一样;
5. 被传输的表空间必须是完备的,如其含有索引等但不包含对应的表;
6. 源数据库在导出元数据和复制数据文件过程中必须将导出的表空间设为只读模式;
7. SYS拥有的对象无法传输;
8. 不能传输的对象有:快照/物化视图、基于函数的索引、区域索引、领域引用(Scoped
Refs)和多个接收者的AQ。
检测表空间是否完备:
exec sys.dbms_tts.transport_set_check( 'tb1', TRUE
);
select * from
sys.transport_set_violations;
exec sys.dbms_tts.transport_set_check( 'tb2', TRUE
);
select * from
sys.transport_set_violations;
exec sys.dbms_tts.transport_set_check( 'tb1, tb2',
TRUE );
select * from
sys.transport_set_violations;
无返回则说明该(套)表空间完备。
开始传输:
alter tablespace tts_ex1 read
only;
alter tablespace tts_ex2 read
only;
host exp userid="""sys/change_on_install as sysdba"""
transport_tablespace=y
tablespaces=(tts_ex1,tts_ex2)
host XCOPY c:\oracle\oradata\tkyte816\tts_ex?.dbf
c:\temp
alter tablespace tts_ex1 read
write;
alter tablespace tts_ex2 read
write;
imp file=expdat.dmp userid="""sys/change_on_install
as sysdba""" transport_tablespace=y
"datafiles=(c:\temp\tts_ex1.dbf,c:\temp\tts_ex2.dbf)"
alter tablespace tts_ex1 read
write;