Oracle數據庫表類型

 Oracle數據庫表類型

6.1 表的類型

     1. 堆組織表

     2. 索引組織表

     3. 聚簇表

     4. 散列聚簇表

     5. 嵌套表

     6. 臨時表

     7. 對象表

     8. 外部表

    一張表最多有1000列;表的行數理論上沒有限制;表上索引個數可以是列的全排列數,而且一次性能夠使用32個;表的數量沒有限制。


6.2 術語

 

    高水位標記 High Water Mark: 曾經包含數據的最右邊的塊。在全表掃描時,Oracle將掃描高水標記一下的所有塊,即使它們不含數據。TRUNCATE將重新設置高水標記。

 

自由列表 Freelist: 在Oracle中用來跟蹤高水標記以下有空閒空間的塊對象。保留在高水標記以上的塊,只有Freelist爲空時才能被用到。

並行更新數據時,配置多個Freelist能提高整體性能,代價是增加了存儲空間。

6.3 堆組織表

應 用中99%(或者更多)的情況下使用的可能都是堆組織表,不過隨着IOT的出現,這種狀況以後可能會有所改觀,因爲IOT本身就可以加索引。執行 CREATE TABLE語句時,默認得到的表類型就是堆組織表。如果你想要任何其他類型的表結構,就需要在CREATE語句本身中指定它。
堆 (heap)是計算機科學領域中得到深入研究的一種經典數據結構。它實際上就是一個很大的空間、磁盤或內存區(當然,這裏所說的磁盤是指數據庫表的相應磁 盤),會以一種顯然隨機的方式管理。數據會放在最合適的地方,而不是以某種特定順序來放置。許多人希望能按數據放入表中的順序從表中取出數據,但是對於 堆,這是無法保證的。


6.4 索引組織表

數據在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本身中。

 

索引組織表小結
在 建立IOT時,最關鍵的是適當地分配數據,即哪些數據存儲在索引塊上,哪些數據存儲在溢出段上。對溢出條件不同的各種場景進行基準測試,查看對 INSERT、UPDATE、DELETE和SELECT分別有怎樣的影響。如果結構只建立一次,而且要頻繁讀取,就應該儘可能地把數據放在索引塊上(最 合適獲取),要麼頻繁地組織索引中的數據(不適於修改)。堆


6.5 索引聚簇表

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.6 散列聚簇表

概念類似索引聚簇表,但用散列函數代替了聚簇碼索引。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.7 嵌套表

兩種使用嵌套表的方法:

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.8 臨時表

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$是內嵌對象類型的標量實現。

 

 

外部表(external table)-- 很大程度上可以替代sqlload

這些表並不存儲在數據庫本身中,而是放在數據庫之外,即放在平常的操作系統文件中。在Oracle9i及以上版本中,利用外部表可以查詢數 據庫之外的一個文件,就好像這個文件也是數據庫中平常的表一樣。外部表對於向數據庫加載數據最有用(外部表是非常強大的數據加載工具)。Oracle 10g則更進一步,還引入了一個外部表卸載功能,在不使用數據庫鏈接的情況下,這爲在Oracle數據庫之間移動數據提供了一種簡單的方法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章