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)
INSERT INTO dept VALUES(x.deptno,x.dname,x.loc);
INSERT INTO emp
SELECT * FROM scott.emp
WHERE deptno=x.deptno;
END
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);
嵌套表存儲
上例中,現實產生了兩張表:
| |
deptno |
NUMBER(2) |
dname |
VARCHAR2(14) |
loc |
VARCHAR2(13) |
|
RAW(16) |
EMPS_NT | |
SYS_NC_ROWINFO$ |
|
|
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 基於函數的索引
在
實現前提
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後的值),系統同樣會識別出基於函數的索引。
對於
對於用戶自定義的函數建立索引後,不能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.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.
# 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
# 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.
# 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<
UNIX下:
$exp userid=… tables=t query=/ ”where object_id/<5000/”
使用參數文件:
exp.par: query=”where object_id<
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;
alter tablespace tts_ex2 read write;
注意到使用的用戶,在
獲得DDL
SHOW=Y和INDEXFILE=文件名兩種選項均可顯示DDL,但前者的顯示斷行不合理、語句均加上了雙引號,因此傾向使用INDEXFILE選項;但IMP在INDEXFILE選項中不顯示觸發器和視圖的DDL。
1.獲取程序包、函數和存儲過程代碼:@getcode procedure_name
REM getcode.sql
SET feedback OFF
SET heading OFF
SET termout OFF
SET linesize 1000
SET trimspool ON
SET verify OFF
SPOOL &1.sql
PROMPT SET DEFINE OFF
SELECT DECODE(type||'-'||TO_CHAR(line,'fm99999'),'PACKAGE BODY-1','/'||CHR(10),NULL)||DECODE(line,1,'create or replace','')||text text
FROM user_source
whre name=UPPER('&&1')
ORDER BY TYPE,line;
PROMPT /
PROMPT SET DEFINE ON
SPOOL OFF
SET feedback ON
SET heading ON
SET termout ON
SET linesize 100
2.獲得視圖DDL:@getaview view_name
REM getaview.sql
SET feedback OFF
SET heading OFF
SET termout OFF
SET linesize 1000
SET trimspool ON
SET verify OFF
SET LONG 99999999
SET embedded ON
SPOOL &1.sql
PROMPT CREATE OR REPLACE VIEW &1(SELECT DECODE(column_id,1,'',',')||column_name column_name FROM user_tab_columns WHERE TABLE table_name=UPPER('&1') ORDER BY column_id;
PROMPT AS SELECT text FROM user_views WHERE view_name=UPPER('&1')
PROMPT /
SPOOL OFF
SET feedback ON
SET heading ON
SET termout ON
SET linesize 100
3. 觸發器DDL:@gettrig trigger_name
REM gettrig.sql
SET feedback OFF
SET heading OFF
SET termout OFF
SET trimspool ON
SET verify OFF
SET LONG 99999999
SPOOL &1.sql
SELECT 'CREATE OR REPLACE TRIGGER('''||trigger_name||'''||chr(10)||decode(substr(trigger_type,1,1),'A','AFTER','B','BEFORE','T',INSTEAD OF')||CHR(10)||triggering_event||CHR(10)||'ON'''||table_owner||'''.'''||table_name||''''||CHR(10)||DECODE(INSTR(trigger_type,'EACH ROW'),0,NULL,'FOR EACH ROW')||CHR(10),trigger_body FROM user_triggers WHERE trigger_name=UPPER('&1')
/
PROMPT /
SPOOL OFF
SET feedback ON
SET heading ON
SET termout ON
導入到不同結構
1. 增加了列
不需要額外工作,通常將增加的列設爲NULL或其他指定的默認值。
2. 減少了列
將修改後的表改名,並用原表名建立視圖,對視圖建立INSTEAD OF INSERT…的觸發器。
3. 改變了列的數據類型
同上2的方法。
直接路徑導出
即DIRECT=Y。此模式繞過了SQL評估緩衝區,QUERY選項失效,但可以節省10%的處理。
8.2 警告和錯誤
克隆
使用EXP/IMP克隆用戶時要注意,完整性約束(特別是顯式聲明參照同一方案下表的外鍵,如REFERENCES FROMUSER.TABLE)將自動根據FROMUSER和TOUSER更改所有者,而參照其他方案下表的外鍵、顯式聲明基於同一方案下表的視圖(如… AS SELECT * FROM FROMUSER.TABLE)和觸發器將不會改變所有者,這樣如果IMP入的庫中有與FROMUSER同名的用戶或TOUSER權限不足,將產生錯誤。因此在運行前,應檢查所有的DDL、觸發器和過程等:
imp userid=… from user=tkyte touser=a indexfile=….sql
imp userid=… from user=tkyte touser=a show=y
跨版本使用IMP/EXP
規則:IMP採用導入數據庫的版本;EXP採用兩個數據庫中較低的版本。
索引的丟失
EXP/IMP後,系統命名的“冗餘”索引將會丟失,原因有二:
1. 系統設定的索引名可能與導入數據庫中已有的系統分配的索引名重複;
2. 對象的創建本身可能已經創建了索引,即再導入隱式索引可能重複。
第2點即說明所謂“冗餘”:在創建表時隱式創建了主鍵,隨後創建一個第一字段就是主鍵列的索引,那麼該表上實際有兩個索引;但變換順序,先創建後面的索引,再顯式創建主鍵,結果是系統用該索引來加強主鍵,並不創建新索引。後者的情況就如同EXP/IMP的選擇,是正確的。
重複導入導致增加冗餘約束
對於含有系統命名約束(如check等)的表,導出後如果多次執行導入,雖然會提示約束名已被佔用而不會重複導入約束,但仍會每次添加一些重複約束,從而導致性能的下降。因此綜合之前幾點,還是應該使用顯式命名的約束。
NLS問題
在導出和導入時看到possible charset conversion的提示就應當注意字符集問題,應將系統NLS_LANG設置與數據庫字符集一致。
表跨越多個表空間
對於單個表空間的表,導入時未找到表空間或配額不足,IMP將重寫SQL,使用導入庫的缺省表空間來導入。而對跨越多個表空間的表(如分區表等),IMP不會如此。唯一方法是事先建立類似表空間結構的表再導入。
9.1 SQL Loader簡介
9.2 如何裝載
裝載定界數據
TERMINATED BY WHITESPACE通過查找下一個非空格字符(即不是製表符、空格或換行)位置來定位。
裝載固定格式數據
POSITION(*:..)中*指示控制文件在最後字段停止位置重新開始,並可用+、-進行相對位置移動。另外POSITION子句可以使用重疊位置,並在記錄中往返。
裝載日期
使用序列和其他函數裝載數據
更新現有的行和插入新行
裝載報表類型的輸入數據
裝載文件到一個長RAW或LONG字段中
裝載含換行符的數據
1. 使用其它字符代替換行符,導入時替換
如果控制了數據文件的產生,可用例如“/n”類的字符來替代換行符,在導入時用
字段名 “replace(:字段名,’/n’,chr(10))”(Win平臺)和字段名 “replace(:字段名,’//n’,chr(10))”(UNIX平臺)
來替代。
2. 使用FIX屬性
使用形如INFILE ….DAT “fix nnn”的選項,則指定數據文件每行長度nnn。必須注意的是,在UNIX平臺下,換行僅爲”/n”,而在Win平臺下爲”/r/n”,這樣用這種方法最好確保生成數據文件的平臺和導入的平臺一致,否則由於每行長度不同容易出錯。
3. 使用VAR屬性
同前,使用INFILE ….DAT “var n”,即數據文件每行的前n個字符說明該行長度。
4. 使用STR屬性
爲數據文件設立新的分隔符,使用INFILE ….DAT “str X’ooo’”,即ooo作爲分隔符,而不是換行符。其中ooo是如下獲得的16進制數:
SELECT UTL_RAW.CAST_TO_RAW(…) FROM DUAL;
5. 內嵌的換行符換行
卸載數據
CREATE OR REPLACE PACKAGE UNLOADER
AS
FUNCTION RUN( P_QUERY IN VARCHAR2,
P_TNAME IN VARCHAR2,
P_MODE IN VARCHAR2 DEFAULT 'REPLACE',
P_DIR IN VARCHAR2,
P_FILENAME IN VARCHAR2,
P_SEPARATOR IN VARCHAR2 DEFAULT ',',
P_ENCLOSURE IN VARCHAR2 DEFAULT '"',
P_TERMINATOR IN VARCHAR2 DEFAULT '|' )
RETURN NUMBER;
END;
/
CREATE OR REPLACE PACKAGE BODY UNLOADER
AS
G_THECURSOR INTEGER DEFAULT DBMS_SQL.OPEN_CURSOR;
G_DESCTBL DBMS_SQL.DESC_TAB;
G_NL VARCHAR2(2) DEFAULT CHR(10);
FUNCTION TO_HEX( P_STR IN VARCHAR2 ) RETURN VARCHAR2
IS
BEGIN
RETURN TO_CHAR( ASCII(P_STR), 'FM0X' );
END;
PROCEDURE DUMP_CTL( P_DIR IN VARCHAR2,
P_FILENAME IN VARCHAR2,
P_TNAME IN VARCHAR2,
P_MODE IN VARCHAR2,
P_SEPARATOR IN VARCHAR2,
P_ENCLOSURE IN VARCHAR2,
P_TERMINATOR IN VARCHAR2 )
IS
L_OUTPUT UTL_FILE.FILE_TYPE;
L_SEP VARCHAR2(5);
L_STR VARCHAR2(5);
L_PATH VARCHAR2(5);
BEGIN
IF ( P_DIR LIKE '%/%' )
THEN
-- WINDOWS PLATFORMS --
L_STR := CHR(13) || CHR(10);
IF ( P_DIR NOT LIKE '%/' AND P_FILENAME NOT LIKE '/%' )
THEN
L_PATH := '/';
END IF;
ELSE
L_STR := CHR(10);
IF ( P_DIR NOT LIKE '%/' AND P_FILENAME NOT LIKE '/%' )
THEN
L_PATH := '/';
END IF;
END IF;
L_OUTPUT := UTL_FILE.FOPEN( P_DIR, P_FILENAME || '.CTL', 'W' );
UTL_FILE.PUT_LINE( L_OUTPUT, 'LOAD DATA' );
UTL_FILE.PUT_LINE( L_OUTPUT, 'INFILE ''' || P_DIR || L_PATH ||
P_FILENAME || '.DAT'' "STR X''' ||
UTL_RAW.CAST_TO_RAW( P_TERMINATOR ||
L_STR ) || '''"' );
UTL_FILE.PUT_LINE( L_OUTPUT, 'INTO TABLE ' || P_TNAME );
UTL_FILE.PUT_LINE( L_OUTPUT, P_MODE );
UTL_FILE.PUT_LINE( L_OUTPUT, 'FIELDS TERMINATED BY X''' ||
TO_HEX(P_SEPARATOR) ||
''' ENCLOSED BY X''' ||
TO_HEX(P_ENCLOSURE) || ''' ' );
UTL_FILE.PUT_LINE( L_OUTPUT, '(' );
FOR I IN 1 .. G_DESCTBL.COUNT
IF ( G_DESCTBL(I).COL_TYPE = 12 )
THEN
UTL_FILE.PUT( L_OUTPUT, L_SEP || G_DESCTBL(I).COL_NAME ||
' DATE ''DDMMYYYYHH24MISS'' ');
ELSE
UTL_FILE.PUT( L_OUTPUT, L_SEP || G_DESCTBL(I).COL_NAME ||
' CHAR(' ||
TO_CHAR(G_DESCTBL(I).COL_MAX_LEN*2) ||' )' );
END IF;
L_SEP := ','||G_NL ;
END
UTL_FILE.PUT_LINE( L_OUTPUT, G_NL || ')' );
UTL_FILE.FCLOSE( L_OUTPUT );
END;
FUNCTION QUOTE(P_STR IN VARCHAR2, P_ENCLOSURE IN VARCHAR2)
RETURN VARCHAR2
IS
BEGIN
RETURN P_ENCLOSURE ||
REPLACE( P_STR, P_ENCLOSURE, P_ENCLOSURE||P_ENCLOSURE ) ||
P_ENCLOSURE;
END;
FUNCTION RUN( P_QUERY IN VARCHAR2,
P_TNAME IN VARCHAR2,
P_MODE IN VARCHAR2 DEFAULT 'REPLACE',
P_DIR IN VARCHAR2,
P_FILENAME IN VARCHAR2,
P_SEPARATOR IN VARCHAR2 DEFAULT ',',
P_ENCLOSURE IN VARCHAR2 DEFAULT '"',
P_TERMINATOR IN VARCHAR2 DEFAULT '|' ) RETURN NUMBER
IS
L_OUTPUT UTL_FILE.FILE_TYPE;
L_COLUMNVALUE VARCHAR2(4000);
L_COLCNT NUMBER DEFAULT 0;
L_SEPARATOR VARCHAR2(10) DEFAULT '';
L_CNT NUMBER DEFAULT 0;
L_LINE LONG;
L_DATEFMT VARCHAR2(255);
L_DESCTBL DBMS_SQL.DESC_TAB;
BEGIN
SELECT VALUE
INTO L_DATEFMT
FROM NLS_SESSION_PARAMETERS
WHERE PARAMETER = 'NLS_DATE_FORMAT';
/*
SET THE DATE FORMAT TO A BIG NUMERIC STRING. AVOIDS
ALL NLS ISSUES AND SAVES BOTH THE TIME AND DATE.
*/
EXECUTE IMMEDIATE
'ALTER SESSION SET NLS_DATE_FORMAT=''DDMMYYYYHH24MISS'' ';
/*
SET UP AN EXCEPTION BLOCK SO THAT IN THE EVENT OF ANY
ERROR, WE CAN AT LEAST RESET THE DATE FORMAT BACK.
*/
BEGIN
/*
PARSE AND DESCRIBE THE QUERY. WE RESET THE
DESCTBL TO AN EMPTY TABLE SO .COUNT ON IT
WILL BE RELIABLE.
*/
DBMS_SQL.PARSE( G_THECURSOR, P_QUERY, DBMS_SQL.NATIVE );
G_DESCTBL := L_DESCTBL;
DBMS_SQL.DESCRIBE_COLUMNS( G_THECURSOR, L_COLCNT, G_DESCTBL );
/*
CREATE A CONTROL FILE TO RELOAD THIS DATA
INTO THE DESIRED TABLE.
*/
DUMP_CTL( P_DIR, P_FILENAME, P_TNAME, P_MODE, P_SEPARATOR,
P_ENCLOSURE, P_TERMINATOR );
/*
BIND EVERY SINGLE COLUMN TO A VARCHAR2(4000). WE DON'T CARE
IF WE ARE FETCHING A NUMBER OR A DATE OR WHATEVER.
EVERYTHING CAN BE A STRING.
*/
FOR I
IN 1 .. L_COLCNT
DBMS_SQL.DEFINE_COLUMN( G_THECURSOR, I, L_COLUMNVALUE, 4000 );
END
/*
RUN THE QUERY - IGNORE THE OUTPUT OF EXECUTE. IT IS ONLY
VALID WHEN THE DML IS AN INSERT/UPDATE OR DELETE.
*/
L_CNT := DBMS_SQL.EXECUTE(G_THECURSOR);
/*
OPEN THE FILE TO WRITE OUTPUT TO AND THEN WRITE THE
DELIMITED DATA TO IT.
*/
L_OUTPUT := UTL_FILE.FOPEN( P_DIR, P_FILENAME || '.DAT', 'W',
32760 );
EXIT WHEN ( DBMS_SQL.FETCH_ROWS(G_THECURSOR) <= 0 );
L_SEPARATOR := '';
L_LINE := NULL;
FOR I IN 1 .. L_COLCNT
DBMS_SQL.COLUMN_VALUE( G_THECURSOR, I,
L_COLUMNVALUE );
L_LINE := L_LINE || L_SEPARATOR ||
QUOTE( L_COLUMNVALUE, P_ENCLOSURE );
L_SEPARATOR := P_SEPARATOR;
END
L_LINE := L_LINE || P_TERMINATOR;
UTL_FILE.PUT_LINE( L_OUTPUT, L_LINE );
L_CNT := L_CNT+1;
END
UTL_FILE.FCLOSE( L_OUTPUT );
/*
NOW RESET THE DATE FORMAT AND RETURN THE NUMBER OF ROWS
WRITTEN TO THE OUTPUT FILE.
*/
EXECUTE IMMEDIATE
'ALTER SESSION SET NLS_DATE_FORMAT=''' || L_DATEFMT || '''';
RETURN L_CNT;
-- EXCEPTION
/*
IN THE EVENT OF ANY ERROR, RESET THE DATA FORMAT AND
RE-RAISE THE ERROR.
*/
-- WHEN OTHERS THEN
-- EXECUTE IMMEDIATE
-- 'ALTER SESSION SET NLS_DATE_FORMAT=''' || L_DATEFMT || '''';
-- RAISE;
END;
END RUN;
END UNLOADER;
/
裝載LOB
1. 利用PL/SQL
CREATE OR REPLACE DIRECTORY 目錄名 AS ‘路徑’;
DECLARE
L_CLOB CLOB;
L_BFILE BFILE;
BEGIN
INSERT INTO DEMO VALUES ( 1, EMPTY_CLOB() )
RETURNING THECLOB INTO L_CLOB;
--若路徑名定義時未用””,則內部轉換爲全大寫
L_BFILE := BFILENAME( '路徑名', '文件名' );
DBMS_LOB.FILEOPEN( L_BFILE );
DBMS_LOB.LOADFROMFILE( L_CLOB, L_BFILE,
DBMS_LOB.GETLENGTH( L_BFILE ) );
DBMS_LOB.FILECLOSE( L_BFILE );
END;
/
2. 使用SQLLDR
i. 裝載同一行的LOB數據
這裏注意LOB數據中往往含有逗號、換行符等,那麼確定分割符時同前,Win平臺爲”str
X’
ii. 裝載不在同行中的LOB數據
即數據文件中某列包含了需要裝載到LOB的文件名。則ctl文件中數據類型說明部分爲“字段名 LOBFILE(含文件名的字段名) TERMINATED BY EOF”。
iii. 裝載LOB數據到對象列
用SQLLDR裝載VARRAYS/嵌套表
在存儲過程中調用SQLLDR
無法調用。只能用PL/SQL、Java、C來實現一個小SQLLDR。
9.3 警告
不能選擇要使用的回滾段
使用REPLACE選項,將在導入前產生DELETE命令,可能產生大量回滾,但Oracle不允許選擇回滾段。
TRUNCATE的不同作用
假設將要裝載相似數量的數據,則可使用TRUNCATE的擴展形式:
TRUNCATE TABLE … REUSE STORAGE
這樣並未釋放空間,只是將空間均標誌爲自由空間。
SQLLDR默認爲CHAR(255)
要導入更長的文本,只需顯式指定CHAR(N)。
命令行取代控制文件
對於例如INFILE等命令行與控制文件均可指定的參數,命令行具有優先級。
10.1 標識問題
10.2 我的方法
10.3 綁定變量與分析(再次)
不使用綁定變量將增加語句分析,除了消耗CPU時間外,還會增加字典高速緩存上的閂鎖。
顯示會話等待的事件:V$SESSION_EVENT。具體事件名和含義可以參考Oracle Reference Manual的附錄Oracle Wait Events。
CURSOR_SHARING
CURSOR_SHARING參數缺省爲EXACT,若指定爲FORCE,則優化器可能將語句中所有的常數轉換爲綁定變量,雖然減少了語句分析,但是也會帶來如下副作用:
優化器可供利用的信息可能減少,從而改變執行路徑,例如條件中對於某個特定值索引有較好的選擇性,改爲綁定變量時優化器並不會發現這一點。
查詢輸出格式發生變化。雖然返回的數據長度不變,但列的長度可能改變。例如對於SELECT id, ‘tom’ name from emp; name應該爲VARCHAR2(3),但是由於’tom’被改爲綁定變量,則可能name的顯示長度變爲32。
查詢計劃更難評估。由於語句的改變,EXPLAIN PLAN看到的查詢與數據庫看到的可能不一致,從而使AUTOTRACE等的輸出與實際執行路徑不一致。
因此,完善的應用系統不應當依靠CURSOR_SHARING來提高效率,僅能作爲權宜之計。
10.4 SQL_TRACE, TIMED_STATISTICS與TKPROF
TIMED_STATISTICS並不會對系統產生過大負擔,因此建議設置爲TRUE。
啓動跟蹤
SQL_TRACE可在系統或會話級激活。激活後跟蹤文件將產生至init.ora參數USER_DUMP_DEST(專用服務器)或BACKGROUND_DUMP_DEST(MTS)指定的目錄。而文件大小通過MAX_DUMP_FILE_SIZE控制,其設置有如下三種方法:
僅數值:以OS塊爲單位;
數值+K/M:指定文件絕對大小;
UNLIMITED:無上限。
一般只需要設置50
激活SQL_TRACE的幾種常用方式如下:
ALTER SESSION SET SQL_TRACE=TRUE|FALSE;
SYS.DBMS_SYSTEM.SET_SQL_TRACE_IN_SESSION 這裏我們需要指定SID和SERIAL#(參考V$SESSION);
ALTER SESSION SET EVENTS. 可獲得更詳細的信息。
此外也可通過DBMS_SUPPORT包,相當於EVENTS跟蹤的一個界面,但此包需要Oracle人員支持,非標配。
隨着WEB服務方式的普及,往往一個數據庫會話很短,難以單獨跟蹤,對此,我們可以根據用戶,在數據庫級建立觸發器:
CREATE OR REPLACE TRIGGER logon_trigger
AFTER LOGON ON DATABASE
BEGIN
IF ( USER= ‘TKYTE’ ) THEN
EXECUTE
IMMEDIATE ‘ALTER SESSION SET EVENTS ‘ ’10046 TRACE NAME CONTEXT FOREVER, LEVEL
END IF;
END;/
使用並解析TKPROF輸出
1. 激活SQL_TRACE後,通過如下查詢檢查SPID:
SELECT a.spid FROM v$process a, v$session b
WHERE a.addr = b.paddr
AND b.audsid = userenv(‘sessionid’);
此SPID就包含在跟蹤文件的文件名中。
UNIX系統中,若你不在Oracle的管理組中,則生成的跟蹤文件所在目錄可能無法訪問,此時需要設定init.ora參數_trace_files_public = true 。
2. TKPROF語法: TKPROF *.trc *.txt
其他用法可以直接運行TKPROF查看。一般常用選項就是-sort,可以根據某些參數值排序。
3. 對跟蹤文件輸出的一些解釋:
i. 行:
PARSE階段:包括了軟分析(在SHARED_POOL中找到語句)和硬分析;
EXECUTE階段:對SELECT幾乎爲空,對UPDATE則幾乎是全部工作的體現;
FETCH階段:對SELECT是幾乎所有的工作,對UPDATE則爲空。
ii. 列:
COUNT:事件發生的次數;
CPU:消耗的CPU時間(CPU秒);
ELAPSED:總體運行時間;
DISK:磁盤物理I/O;
QUERY:一致讀模式訪問的塊數,也包括了從回滾段讀取的塊數;
CURRENT:訪問的當前信息數據塊(而不是一致讀模式),例如SELECT時讀取數據字典內容,修改時也需要訪問數據字典內容以寫。
ROWS:所涉及的行數。
4. 需要注意的現象:
i. 高的PARSE COUNT/EXECUTE COUNT(接近100%),且EXECUTE COUNT大於1
即執行語句時分析的次數,如果過高,可能是軟分析也過多了,對一個會話,應該是分析一次反覆執行。
ii. 對幾乎所有SQL,EXECUTE COUNT都是1
可能沒有使用綁定變量。在一個真實應用中,應該很少看到不同的SQL,同一個SQL應執行多次。
iii. CPU和ELAPSED時間相差較大
說明花了很長時間等待一個事件,例如磁盤I/O、鎖等。
iv. (FETCH COUNT)/(ROWS FETCHED)比例高
沒有很好的使用批量提取。批量提取數據的方法是和語言/API相關的,例如Pro* C中需要使用prefetch=NN預編譯,Java/JDBC下可以調用SETROWPREFETCH方法,PL/SQL可以在SELECT INTO中直接使用BULK COLLECT。而SQL* PLUS缺省爲每次取15行。
v. 極大的DISK COUNT
較難推斷,但若DISK COUNT = QUERY + CURRENT MODE BLOCK COUNT,則說明幾乎所有數據都來自磁盤。此時需要考慮SGA大小和此查詢效率。
vi. 極大的QUERY COUNT或CURRENT COUNT
SQL工作量很大,需要注意。
5. EXPLAIN PLAN問題
跟蹤文件中顯示的是真正執行的路徑。TKPROF也支持EXPLAIN=XXX/XXX選項,不建議使用,其輸出是轉換跟蹤文件當時優化器選擇的執行路徑,並是利用數據庫的EXPLAIN工具,與真實路徑時不完全一致的。
使用與解析原始跟蹤文件
1. EVENTS跟蹤
ALTER SESSION SET EVENTS ‘10046 trace name context forever, level N’;
N=1 同標準SQL_TRACE;
N=4 增加獲得綁定變量值;
N=8 增加獲得查詢級的等待事件;
N=12 增加獲得綁定變量值和查詢級的等待事件。
2. 原始跟蹤文件分段解析
文件頭含有時間、數據庫版本、OS版本、實例名等。
APPNAME mod=’%s’ mh=%lu act=’%s’ ah=%lu
mod |
傳入DBMS_APPLICATION_INFO的模塊名 |
mh |
模塊哈希值 |
act |
傳入DBMS_APPLICATION_INFO的動作 |
ah |
動作哈希值 |
Parsing in Cursor #%d dep=%d uid=%ld oct=%d lid=%ld tim=%ld hv=%ld ad=’%s’
Cursor # |
遊標號。也可以用此值獲知應用最大打開的遊標數。 |
len |
下面SQL語句的長度 |
dep |
SQL語句的遞歸(recursive)深度 |
uid |
當前方案的用戶ID。注意,這並不一定和後面的lid一致,因爲可以用 alter session set current_schema來修改分析時的方案 |
oct |
Oracle命令類型(Oracle Command Type) |
lid |
用於安全性檢查訪問權限的用戶ID |
tim |
定時器,1/100秒 |
ha |
SQL語句的哈希ID |
ad |
V$SQLAREA中此SQL語句的ADDR列 |
EXEC Cursor#:c=%d,e=%d,p=%d,cr=%d,mis=%d,r=%d,dep=%d,og=%d,tim=%d
Cursor # |
遊標號 |
c |
CPU時間,1/100秒 |
e |
流逝(Elapsed)時間,1/100秒 |
p |
物理讀 |
cr |
一致(QUERY模式)讀(邏輯I/O) |
cu |
當前(Current)模式讀(邏輯I/O) |
mis |
字典緩存中的遊標不命中數,說明由於過期已從共享池中清除或從未進入共享池等,而不得不分析此語句 |
r |
處理的行數 |
dep |
SQL語句的遞歸深度 |
og |
優化器目標:1=ALL ROWS 2=FIRST ROWS 3=RULE 4=CHOOSE |
tim |
定時器 |
與EXEC段類似的還有(即取代“EXEC”):
PARSE |
分析一個語句 |
FETCH |
從一個遊標取出數據行 |
UNMAP |
用於顯示在不需要時從中間結果釋放臨時段 |
SORT UMAP |
同UNMAP,指排序段 |
WAIT Cursor#: nam=’%s’ ela=%d p1=%ul p2=%ul p3=%ul
Cursor# |
遊標號 |
nam |
等待事件名 |
ela |
流逝時間,1/100秒 |
p1,p2,p3 |
等待事件特定的參數 |
以上爲文件頭與ALTER SESSION出現的跟蹤信息。此後開始出現運行的SQL語句。
BIND段
cursor# |
遊標號 |
bind N |
綁定位置,從0開始 |
dty |
數據類型 |
mxl |
綁定變量最大長度 |
mal |
最大數組長度(當使用數組綁定或BULK操作時) |
scl |
數值範圍(scale) |
pre |
精度(precision) |
oacflg |
內部標記。若此值爲奇數,則綁定變量可能爲NULL(允許爲NULL) |
oacfl2 |
內部標記續 |
size |
緩衝區大小 |
offset |
用於逐片(piecewise)綁定 |
bfp |
綁定地址 |
bln |
綁定緩衝區大小 |
avl |
真實值長度 |
flag |
內部標記 |
value |
綁定值的字符串表示(如果可能,會是一個十六進制dump) |
其中dty:SELECT text FROM ALL_VIEWS WHERE view_name = ‘USER_VIEWS’ 可看到一個將dty數值轉換爲字符串表示的函數。
此後我們可以看到WAIT段,即真正的等待事件。
對於ENQUEUE事件,實際就是鎖。可用以下函數(傳入參數爲p1)判斷類型:
CREATE OR REPLACE FUNCTION
enqueue_decode(l_p
AS
l_str varchar2(25);
BEGIN
SELECT CHR(BITAND(l_p1, -16777216) / 16777215) ||
CHR(BITAND(l_p1, 16711680) / 65535) || ‘ ‘ ||
DECODE(BITAND(l_p1, 65535),
0, ‘No lock’,
1, ‘No lock’,
2, ‘Row-Share’,
3, ‘Row-Exclusive’,
4, ‘Share’,
5, ‘Share Row-Excl’,
6, ‘Exclusive’ )
INTO l_str
FROM DUAL;
RETURN l_str;
END;
XCTEND(事務邊界)段記錄了提交等:
rlbk |
回滾標記:0 提交 1 回滾 |
rd_only |
只讀標記:0 變化提交或回滾 1 事務只讀 |
STAT段記錄了運行時SQL真正的執行計劃:
cursor # |
遊標號 |
id |
執行計劃行號 |
cnt |
查詢計劃中流經此步驟的行數 |
pid |
此步驟的父ID |
pos |
執行計劃中的位置 |
obj |
訪問的對象的對象ID |
op |
操作的文本描述 |
PARSE ERROR段
len |
SQL語句長度 |
dep |
SQL語句遞歸深度 |
uid |
分析的方案 |
oct |
Oracle命令類型 |
lid |
權限方案ID |
tim |
定時器 |
err |
ORA錯誤代碼 |
ERROR段
cursor # |
遊標數 |
err |
ORA錯誤代碼 |
tim |
定時器 |
10.5 DBMS_PROFILER
10.6 StatsPack
10.7 V$表
V$EVENT_NAME
說明事件名和p1、p2、p3三個參數。
V$FILESTAT和V$TEMPSTAT
說明系統I/O概況。
V$LOCK
說明系統鎖的情況。但注意Oracle並不在外部保存行鎖,此視圖可以找到TM(DML Enqueue)鎖,即說明產生了行鎖。
V$MYSTAT
說明當前會話的統計信息。需要V_$STATNAME(不用V$STATNAME,只是V_$STATNAME的一個同義詞)和V_$MYSTAT上的SELECT權限。
CREATE VIEW MY_STATS AS
SELECT a.name, b.value
FROM V$STATNAME a, V$MYSTAT b
WHERE a.statistic# = b.statistic#;
V$OPEN_CURSOR
記錄所有會話打開的遊標。由於Oracle也會緩存已關閉的遊標,因此此視圖中也會包含已關閉的遊標信息。
V$PARAMETER
說明了所有的init.ora參數。
V$SESSION
記錄數據庫的每個會話。需要對V_$SESSION的SELECT權限。
V$SESSION_EVENT
說明會話的事件情況。
V$SESSION_LONGOPS
記錄CBO認爲執行時間超過6秒的命令及進展。
V$SESSION_WAIT
記錄所有正在等待某事件的會話及已等待時間。
V$SESSTAT
類似V$MYSTAT,但顯示所有會話。
V$SESS_IO
說明會話的I/O信息
V$SQL和V$SQLAREA
記錄SQL信息。建議使用V$SQL,V$SQLAREA是從V$SQL合併而來的視圖,代價較高,對已經繁忙的系統是一個負擔。
V$STATNAME
說明了統計號到統計名的映射。
V$SYSSTAT
記錄實例層面的統計信息。當數據庫關閉時才清空,也是StatsPack很多數據的來源。
V$SYSTEM_EVENT
記錄實例層面的等待事件信息。也是StatsPack很多數據的來源。
第 11 章 優化器計劃穩定性
11.1 概覽
CREATE OR REPLACE ONLINE MyOutLine
FOR CATEGORY mycategory
ON
SELECT ……;
需要CREATE OUTLINE權限
使用時指定會話的CATEGORY即可:
ALTER SESSION SET USE_STORED_OUTLINES = mycategory;
11.2 OPS的使用
對已封裝的應用中SQL進行的優化方法
ALTER SESSION SET CREATE_STORED_OUTLINES = test;
執行應用,如一個存儲過程等
ALTER SESSION SET CREATE_STORED_OUTLINES = FALSE;
SET LONG 5000
SELECT name, sql_text FROM user_outlines WHERE category = ‘test’;
此時可以看到所運行的SQL語句。也可以通過一個ON LOGON觸發器來實現,即一登陸就ALTER SESSION…
優化時修改OPTIMIZER_GOAL後:
ALTER SESSION SET OPTIMIZER_GOAL = FIRST_ROWS;
ALTER OUTLINE name REBUILD;
ALTER SESSION SET OPTIMIZER_GOAL = CHOOSE;
此時就固定爲OPTIMIZER_GOAL = FIRST_ROWS時的執行計劃了。
一個開發工具
由於開發環境與實際部署環境可能不一致,爲了保證執行計劃與開發環境一致,可以建立一個ON LOGON觸發器來將執行計劃歸入一個category中,然後exp/imp到新環境中。
用來觀察是否使用了索引
SELECT name, hint FROM user_outline_hints
WHERE hink LIKE ‘INDEX%’;
用來觀察應用使用了什麼SQL語句
11.3 OPS如何工作
OUTLINES與OUTLINE_HITS
均分別有DBA_、USER_、ALL_三張視圖,其中DBA_多一個owner字段,說明創建者,另兩張與用戶有關係。
DBA_OUTLINES:
NAME |
OUTLINE名,若創建時未指定,則使用系統命名 |
OWNER |
創建時的方案名 |
CATEGORY |
創建的列別,若未指定則爲DEFAULT |
USED |
是否使用過 |
TIMESTAMP |
創建的時間 |
VERSION |
創建時的數據庫版本 |
SQL_TEXT |
SQL查詢語句 |
DBA_OUTLINE_HINTS:
NAME |
OUTLINE名,若創建時未指定,則使用系統命名 |
OWNER |
創建時的方案名 |
NODE |
提示應用的層次,從最外層查詢(1)開始累加計數 |
STAGE |
提示應用的階段,即提示在編譯的哪個階段寫入 |
JOIN_POS |
提示應用的表名,對非訪問方式提示爲0 |
HINT |
提示 |
11.4 創建存儲概要
相關的權限
CREATE ANY OUTLINE – 創建概要,否則報ORA-18005錯誤
ALTER ANY OUTLINE – 修改或重新計算概要
DROP ANY OUTLINE – 刪除概要
EXECUTE ON OUTLN_PKG – 執行OUTLINE包
注意這裏權限都是全局的,概要不存在真正的所有者。
使用DDL
CREATE <OR REPLACE> OUTLINE OUTLINE_NAME
<FOR CATEGORY CATEGORY_NAME>
ON STATEMENT
使用ALTER SESSION
ALTER SESSION SET CREATE_STORED_OUTLINES = TRUE;
ALTER SESSION SET CREATE_STORED_OUTLINES = FALSE;
ALTER SESSION SET CREATE_STORED_OUTLINES = mycategory;
當設爲TRUE時,所創建的概要歸類入DEFAULT。
11.5 OUTLN用戶
所有8i數據庫中均缺省創建,缺省密碼爲OUTLN,並可在安裝後立即更改。方案含有兩個表和一些索引,存放於SYSTEM表空間中,若需要大量使用概要,可用如下方法轉移表空間(其中一張表含有LONG類型字段,無法ALTER TABLE MOVE)。
EXP USERID=OUTLN/OUTLN OWNER=OUTLN
ALTER USER OUTLN DEFAULT TABLESPACE tools;
REVOKE UNLIMITED TABLESPACE FROM OUTLN;
ALTER USER OUTLN QUOTA 0K ON SYSTEM;
ALTER USER OUTLN QUOTA UNLIMITED ON tools;
DROP TABLE ol$;
DROP TABLE ol$hints;
IMP USERID=OUTLN/OUTLN FULL=YES
若系統已經使用了概要,則操作應儘量在單用戶模式下執行,數據庫無其它活動終端用戶。
11.6 在數據庫間轉移概要
EXP USERID=OUTLN/OUTLN QUERY=”where category=’test’” tables=(ol$, ol$hints)
IMP USERID=OUTLN/OUTLN FULL=Y IGNORE=YES
這裏也可以使用參數文件來定義導出的查詢條件。
11.7 獲得正確的概要
有時僅修改某些參數是無法獲得所需要的執行計劃的,還要添加提示。但概要的使用是基於相同的SQL文本,爲了不修改應用但使用添加了提示的執行計劃,可以採用如下方法:
例如需要SELECT FROM (SELECT /*+ use_hash(emp) */ FROM emp) emp,
(SELECT /*+ use_hash(dept) */ FROM dept) dept
WHERE emp.deptno=dept.deptno;
則可以在另一個方案中刪除emp、dept表,將內層查詢語句建立成名爲emp和dept的視圖,然後對SELECT * FROM emp,dept WHERE emp.deptno=dept.deptno; 建立概要。則此後可以指定應用使用此概要(SQL文本一致)。
這也是利用了OPS是全局的,並不關心所引用對象,而是純粹根據SQL文本進行轉換。
11.8 管理概要
通過DDL
ALTER OUTLINE outline_name RENAME TO new_name;
ALTER OUTLINE outline_name CHANGE CATEGORY TO new_category_name;
ALTER OUTLINE outline_name REBUILD;
DROP OUTLINE outline_name;
OUTLN_PKG包
作用:提供批量管理的功能;提供EXP/IMP的API
由DBMSOL.SQL和PRVTOL.PLB腳本(%ORACLE_HOME%/RDBMS/ADMIN)創建,而這兩個腳本由CATPROC.SQL調用並缺省安裝到數據庫。
DROP_UNUSED – 刪除所有類別中所有未使用的概要。
EXEC OUTLN_PKG.DROP_UNUSED;
DROP_BY_CAT – 刪除指定類別中的所有概要。
EXEC OUTLN_PKG.DROP_BY_CAT(category_name);
UPDATE_BY_CAT – 重命名一個類別或將其合併入另一個類別。
EXEC OUTLN_PKG.UPDATE_BY_CAT(old_category_name, new_category_name); 若新名已被用,則合併,且若新舊類別存在相同SQL文本的概要,保留新類別中的,而此重複的概要仍保留於原類別中。
11.9 最後說明
創建概要需要CREATE ANY OUTLINE權限,若無權限,利用ALTER SESSION方式來創建概要時不會提示錯誤,但不會創建概要。
刪除用戶時即便指定CASCADE選項,也不會刪除其創建的概要。
如果CURSOR_SHARING設爲force,則用DDL和ALTER SESSION兩種方法獲得的SQL文本可能是不同的,前者就是輸入的SQL,而後者是系統內部已經轉換過綁定變量的SQL。
概要的使用依靠文本完全匹配,即便是大小寫不同也會造成SQL文本不匹配。
OR擴展問題:由於WHERE條件中有OR的SQL會被改寫爲UNION ALL模式,概要記錄的提示可能無法正常使用,而只是作用到第一個條件上。因此要注意USER_OUTLINE_HINTS表中HINT LIKE ‘USE_CONCAT%’的概要和提示,應當刪除或移走。
使用概要對性能影響很小。創建概要時接近首次分析該條語句的時間,此後第一次分析慢於正常分析時間,而隨後概要已經進入緩存,將不會觀察到性能影響。
11.10 可能遇到的錯誤
ORA-18001 – 使用ALTER OUTLINE語法錯誤
ORA-18002 – 所引用的概要不存在(從未創建過或者被刪除)
ORA-18003 – 概要的數字簽名已存在,數字簽名用於快速查找到合適的概要,此錯誤極少發生
ORA-18004 – 概要已存在,一般是命名衝突
ORA-18005 – 需要CREATE ANY OUTLINE權限
ORA-18006 – 需要DROP ANY OUTLINE權限
ORA-18007 – 需要ALTER ANY OUTLINE權限
第 12 章 分析函數
12.1 分析函數如何工作
語法
FUNCTION_NAME(<參數>,…)
OVER
(<PARTITION BY 表達式,…> <ORDER BY 表達式 <ASC | DESC> <NULLS FIRST | NULLS LAST>> <WINDOWING子句>)
PARTITION子句
ORDER BY子句
WINDOWING子句
缺省時相當於RANGE UNBOUNDED PRECEDING
1. 值域窗(RANGE WINDOW)
RANGE N PRECEDING
僅對數值或日期類型有效,選定窗爲排序後當前行之前,某列(即排序列)值大於/小於(當前行該列值 –/+ N)的所有行,因此與ORDER BY子句有關係。
2. 行窗(ROW WINDOW)
ROWS N PRECEDING
選定窗爲當前行及之前N行。
還可以加上BETWEEN AND 形式,例如RANGE BETWEEN m PRECEDING AND n FOLLOWING
函數
AVG(<distinct | all> expr) |
一組或選定窗中表達式的平均值 |
CORR(expr, expr) |
即COVAR_POP(exp1,exp2) / (STDDEV_POP(expr1) * STDDEV_POP(expr2)),兩個表達式的互相關,-1(反相關) ~ 1(正相關),0表示不相關 |
COUNT(<distinct> <*> <expr>) |
計數 |
COVAR_POP(expr, expr) |
總體協方差 |
COVAR_SAMP(expr, expr) |
樣本協方差 |
CUME_DIST |
累積分佈,即行在組中的相對位置,返回0 ~ 1 |
DENSE_RANK |
行的相對排序(與ORDER BY搭配),相同的值具有一樣的序數(NULL計爲相同),並不留空序數 |
FIRST_VALUE |
一個組的第一個值 |
LAG(expr, <offset>, <default>) |
訪問之前的行,OFFSET是缺省爲1 的正數,表示相對行數,DEFAULT是當超出選定窗範圍時的返回值(如第一行不存在之前行) |
LAST_VALUE |
一個組的最後一個值 |
LEAD(expr, <offset>, <default>) |
訪問之後的行,OFFSET是缺省爲1 的正數,表示相對行數,DEFAULT是當超出選定窗範圍時的返回值(如最後行不存在之前行) |
MAXexpr) |
最大值 |
MIN(expr) |
最小值 |
NTILE(expr) |
按表達式的值和行在組中的位置編號,如表達式爲4,則組分4份,分別爲1 ~ 4的值,而不能等分則多出的部分在值最小的那組 |
PERCENT_RANK |
類似CUME_DIST,1/(行的序數 - 1) |
RANK |
相對序數,允許並列,並空出隨後序號 |
RATIO_TO_REPORT(expr) |
表達式值 / SUM(表達式值) |
REGR_ xxxx(expr, expr) |
線性迴歸函數 |
ROW_NUMBER |
排序的組中行的偏移 |
STDDEV(expr) |
標準差 |
STDDEV_POP(expr) |
總體標準差 |
STDDEV_SAMP(expr) |
樣本標準差 |
SUM(expr) |
合計 |
VAR_POP(expr) |
總體方差 |
VAR_SAMP(expr) |
樣本方差 |
VARIANCE(expr) |
方差 |
12.2 例子
豎錶轉橫表
一般形式爲將一個列爲C!, C2, … CN的表,以C1, C2, … CX爲基準,將CX+1, … CN的不同值改爲列。一般化的語法:
SELECT C1, C2, … CX,
MAX(DECODE(rn, 1, CX+1, NULL)) CX+1_1, … MAX(DECODE(rn, 1, CN, NULL)) CN_1
MAX(DECODE(rn, 2, CX+1, NULL)) CX+1_2, … MAX(DECODE(rn, 2, CN, NULL)) CN_2
…
MAX(DECODE(rn,N,CX+1, NULL)) CX+1_N, … MAX(DECODE(rn,N, CN, NULL)) CN_N
FROM
(SELECT C1, C2, … CN,
ROW_NUMBER() OVER (PARTITION BY C1, C2, … CX ORDER BY <something>) rn
FROM T
WHERE …)
GROUP BY C1, C2, … CX;
通用包:
CREATE OR REPLACE PACKAGE pkg_pivot
AS
TYPE refcursor IS REF CURSOR;
TYPE ARRAY IS TABLE OF VARCHAR2(30);
PROCEDURE pivot(p_max_cols IN NUMBER DEFAULT NULL,
p_max_cols_query IN VARCHAR2 DEFAULT NULL,
p_query IN VARCHAR2,
p_anchor IN ARRAY,
p_pivot IN ARRAY,
p_cursor IN OUT refcursor);
END;
CREATE OR REPLACE PACKAGE BODY pkg_pivot
AS
PROCEDURE pivot(p_max_cols IN NUMBER DEFAULT NULL,
p_max_cols_query IN VARCHAR2 DEFAULT NULL,
p_query IN VARCHAR2,
p_anchor IN ARRAY,
p_pivot IN ARRAY,
p_cursor IN OUT refcursor)
AS
l_max_cols NUMBER;
l_query LONG;
l_cnames ARRAY;
BEGIN
IF (p_max_cols IS NOT NULL)
THEN
EXECUTE IMMEDIATE p_max_cols_query INTO l_max_cols;
ELSE
RAISE_APPLICATION_ERROR(-20001, 'Cannot figure out max cols');
END IF;
l_query := 'select ';
FOR i IN 1 .. p_anchor.count
l_query := l_query || p_anchor(i) || ',';
END
FOR i IN 1 .. l_max_cols
FOR j IN 1 .. p_pivot.count
l_query := l_query || 'max(decode(rn,'||i||','||p_pivot(j)||',null)) '||p_pivot(j) || '_' || i || ',';
END
END
l_query := RTRIM(l_query,',') || ' from (' || p_query || ') group by ';
FOR i IN 1 .. p_anchor.count
l_query := l_query || p_anchor(i) || ',';
END
l_query := RTRIM(l_query,',');
EXECUTE IMMEDIATE 'alter session set cursor_sharing=force';
OPEN p_cursor FOR l_query;
EXECUTE IMMEDIATE 'alter session set cursor_sharing=exact';
END;
END;
其中:
p_max_cols_query爲SELECT MAX(COUNT(*)) FROM TABLE_NAME GROUP BY C1, C2, … CX;
p_query爲SELECT C1, C2, … CN ROW_NUMBER() OVER (PARTITION BY C1, C2, … CX ORDER BY <something>) rn FROM TABLE_NAME;
p_anchor爲pkg_pivot.array(C1, C2, … CX)
p_pivot爲pkg_pivot.array(CX+1, CX+2, … CN)
p_cursor爲返回的遊標。
12.3 最後說明
PL/SQL與分析函數
PL/SQL不支持分析函數的語法,可以通過以下兩種方法解決:
1.使用動態遊標;
2.將含分析函數的語句創建爲視圖。
WHERE子句中的分析函數
由於查詢僅在最後的ORDER BY子句前執行分析函數,因此WHERE條件中無法使用分析函數,只能利用嵌套循環實現。
第 13 章 物化視圖
需要權限:GRANT CREATE MATERIALIZED VIEW,還必須直接賦予GRANT QUERY REWRITE。爲實現查詢重寫,必須使用CBO。
13.1 物化視圖如何工作
設置
COMPATIBLE參數必須高於
QUERY_REWRITE_ENABLED = TRUE
QUERY_REWRITE_INTEGRETY =
ENFORCED - 查詢僅用Oracle強制與保證的約束、規則重寫;
TRUSTED – 查詢除用Oracle強制與保證的約束、規則,也可用用戶設定的數據間的任何關係來重寫;
STALE_TOLERATED – 即便Oracle知道物化視圖中數據過期(與事實表等不同步),也重寫查詢。
創建物化視圖的用戶必須具有直接賦予的GRANT QUERY REWRITE權限,不能通過角色繼承。
內部機制
全文匹配
部分匹配:從FROM子句開始,優化器比較之後的文本,然後比較SELECT列表
一般重寫方法:
數據充分
關聯兼容
分組兼容
聚集兼容
13.2 確保使用物化視圖
約束
考慮到現實環境的數據量,可以將主鍵、外鍵、非空等約束置爲NOVALIDATE,並調整QUERY_REWRITE_INTEGRITY爲TRUSTED,這樣可以達到“欺騙”數據庫的目的,但必須注意如果無法保證此類約束的真實有效,查詢改寫後可能造成結果不精確。
維度
實際就是指明已存在的表中各列的歸併關係,從而關聯事實表後形成的物化視圖可用於向“上”歸併(相當於用表中代表更高歸併關係的列關聯事實表)。標準語法:
CREATE DIMENSION time_hierarchy_dim
LEVEL day IS time_hierarchy.day
LEVEL mmyyyy IS time_hierarchy.mmyyyy
LEVEL yyyy IS time_hierarchy.yyyy
HIERARCHY time_rollup
(day CHILD OF mmyyyy CHILD OF yyyy)
ATTRIBUTE mmyyyy
DETERMINES mon_yyyy;
13.3 DBMS_OLAP
估計(物化視圖)大小
DBMS_OLAP.ESTIMATE_SUMMARY_SIZE(視圖名, 視圖定義, 估計行數, 估計字節數);
其中後兩個參數爲NUMBER型輸出參數。
維度有效性檢查
DBMS_OLAP.VALIDATE_DIMENSION(視圖名, 用戶名, FALSE, FALSE);
SELECT * FROM 維度表名
WHERE ROWIN IN (SEELCT bad_rowid FROM MVIEW$_EXCEPTION);
所選出行即爲不符合維度定義的行。
推薦物化視圖
首先必須添加合適的外鍵,包通過外鍵來判定表之間的關係而不是維度。
DBMS_OLAP.RECOMMEND_MV(事實表名, 1000000000, ‘’);
第二個參數表示物化視圖可用的空間大小,可傳入一個較大的數。第三個參數傳入需要保留的特定物化視圖,傳入空即爲不考慮其他物化視圖。
執行C:/oracle/RDBMS/demo/sadvdemo後執行:
DEMO_SUMADV.PRETTYPRINT_RECOMMENDATIONS
13.4 最後說明
物化視圖不爲OLTP系統設計
在事實表等更新時會導致物化視圖行鎖,從而影響系統併發性。
第 14 章 分區
14.1 分區的使用
增加可用性
減輕維護負擔
提高DML與查詢的性能
14.2 分區如何工作
表分區策略
索引分區
本地索引
分爲本地前綴索引(Local Prefixed Index)、本地非前綴索引(Local Non-prefixed Index)
1. 索引的選擇
在單表查詢中,本地非前綴索引可能增加可用性,也更加實用。例如表T(a, b)按a區間分區,若在b上建立本地索引,則當某個分區離線,僅查詢b的某個值時,該索引可用,而索引(a, b)不可用;刪除索引(a, b),查詢(a, b)的某對值,b上的索引仍可用。此時若建立索引(b, a),則可應對各類查詢。
在多表關聯(如上例中按照(a, b)值關聯)時,系統將發現代價較高而不會用到本地非前綴索引(如上例中(b, a))。
因此建立本地索引時應當考慮通常的使用環境。
2. 無法基於本地非前綴索引建立唯一鍵或主鍵。
全局索引
僅有一種,即全局前綴索引
1. 數據倉庫環境
在(與建立好相應索引的表)交換分區與索引或分割分區後,全局索引將失效而必須重建,因此全局索引並不適合數據倉庫。
例如:
ALTER TABLE partitioned
EXCHANGE PARTITION fy_1999
WITH TABLE fy_1999
INCLUDING INDEXES
WITHOUT VALIDATION;
ALTER TABLE partitioned
SPLIT PARTITION the_rest
AT
(TO_DATE(‘
INTO (PARTITION fy_2000, PARTITION the_rest);
2. OLTP環境
一定程度上增加了可用性。當某些分區離線,不含有用於分區的列且合乎查詢條件的數據存在於在線分區的索引仍然是可用的,對於不需要查詢全表而是通過索引即可得到結果的查詢也是有效的(例如COUNT非用於分區的列等)。
第 15 章 自治事務
DECLARE
PRAGMA AUTONOMOUS_TRANSACTION;
15.1 爲何使用自治事務
無法回滾的審計
一般情況下利用觸發器禁止某些對錶的更新等操作時,若記錄日誌,則觸發器最後拋出異常時會造成日誌回滾。利用自治事務可防止此點。
避免變異表
即在觸發器中操作觸發此觸發器的表
在觸發器中使用DDL
寫數據庫
對數據庫有寫操作(INSERT、UPDATE、DELETE、CREATE、ALTER、COMMIT)的存儲過程或函數是無法簡單的用SQL來調用的,此時可以將其設爲自治事務,從而避免ORA-14552(無法在一個查詢或DML中執行DDL、COMMIT、ROLLBACK)、ORA-14551(無法在一個查詢中執行DML操作)等錯誤。需要注意的是函數必須有返回值,但僅有IN參數(不能有OUT或IN/OUT參數)。
開發更模塊化的代碼
在大型開發中,自治事務可以將代碼更加模塊化,失敗或成功時不會影響調用者的其它操作,代價是調用者失去了對此模塊的控制,並且模塊內部無法引用調用者未提交的數據。
15.2 如何工作
事務控制
DECLARE整個塊都是屬於父事務的,自治事務從離PRAGMA後的第一個BEGIN開始,只要此BEGIN塊仍在作用域,則都屬於自治事務。例如在DECLARE模塊中聲明一個寫數據庫的函數,則此函數雖然在自治事務所在存儲過程執行,但其屬於父事務;而自治事務中調用的任何函數和存儲過程、激發的任何觸發器等均爲此自治事務的一部分。
自治事務可以嵌套,嵌套深度等只受INIT.ORA參數TRANSACTIONS(同時併發的事務數,缺省爲SESSIONS的1.1倍)制約。
作用域
1. 包中的變量
自治事務可看到並修改父事務的變量,父事務也會察覺到這一改變,且不存在回滾問題。
2. 會話設置/參數
自治事務與父事務共享同一個會話環境,通過ALTER SESSION作的修改對整個會話均有效。但SET TRANSACTION是事務級的,僅對提起修改的事務有效。
3. 數據庫修改
父事務已提交的修改對自治事務可見,未提交的對自治事務不可見,自治事務的修改對父事務是否可見取決於隔離級別(Isolation Level)。
對於遊標,取決於其打開的位置,若其在父事務中打開,則之前父事務未提交的修改對其是有效的,在自治事務中這些修改也可見;而在自治事務中打開,則父事務未提交的修改不可見。
若使用缺省的READ COMMITTED隔離級別,則自治事務的修改對父事務可見;若改用SERIALIZABLE,則不可見。
4. 鎖
父事務與自治事務是完全不同的事務,因此無法共享鎖等。
結束一個自治事務
必須提交一個COMMIT、ROLLBACK或執行DDL。
保存點
無法在自治事務中回滾到父事務中的一個保存點,只能在內部使用保存點。
15.3 最後說明
不支持分佈式事務
截至
僅可用PL/SQL
全部事務回滾
若自治事務出錯,則全部回滾,即便父事務有異常處理模塊。
事務級臨時表
每個會話僅一個事務可訪問事務級臨時表(多個會話中的事務可併發操作)。
變異表
15.4 可能遇到的錯誤
ORA-06519 – 檢查到活動自治事務,回滾——退出自治事務時沒有提交、回滾或DDL操作
ORA-14450 – 試圖訪問正在使用的事務級臨時表
ORA-00060 – 等待資源時檢查到死鎖
16.1 爲何使用動態SQL
實現動態SQL有兩種方式:DBMS_SQL和本地動態SQL(EXECUTE IMMEIDATE)
主要從以下方面考慮使用哪種方式:
1. 是否知道涉及的列數和類型
DBMS_SQL包括了一個可以“描述”結果集的存儲過程(DBMS_SQL.DESCRIBE_COLUMNS),而本地動態SQL沒有。
2. 是否知道可能涉及的綁定變量數和類型
DBMS_SQL允許過程化的綁定語句的輸入,而本地動態SQL需要在編譯時確定。
3. 是否使用“數組化”操作(Array Processing)
DBMS_SQL允許,而本地動態SQL基本不可以,但可以用其他方式實現(對查詢可用FETCH BULK COLLECT INTO,對INSERT等,可用一個BEGIN … END塊中加循環實現)。
4. 是否在同一個會話中多次執行同一語句
DBMS_SQL可以分析一次執行多次,而本地動態SQL會在每次執行時進行軟分析。
5. 是否需要用REF CURSOR返回結果集
僅本地動態SQL可用REF CURSOR返回結果集。
16.2 如何使用動態SQL
DBMS_SQL
1. 調用OPEN_CURSOR獲得一個遊標句柄;
2. 調用PARSE分析語句。一個遊標句柄可以用於多條不同的已分析語句,但一個時間點僅一條有效;
3. 調用BIND_VARIABLE或BIND_ARRAY來提供語句的任何輸入;
4. 若是一個查詢(SELECT語句),調用DIFINE_COLUMN或DEFINE_ARRAY來告知Oracle如何返回結果;
5. 調用EXECUTE執行語句;
6. 若是一個查詢,調用FETCH_ROWS來讀取數據。可以使用COLUMN_VALUE從SELECT列表根據位置獲得這些值;
7. 否則,若是一個PL/SQL塊或帶有RETURN子句的DML語句,可以調用VARIABLE_VALUE從塊中根據變量名獲得OUT值;
8. 調用CLOSE_CURSOR。
注意這裏對任何異常都應該處理,以關閉遊標,防止泄露資源。
本地動態SQL
EXECUTE IMMEDIATE ‘語句’
[INTO {變量1, 變量2, … 變量N | 記錄體}]
[USING [IN | OUT | IN OUT] 綁定變量1, … 綁定變量N]
[{RETURNING | RETURN} INTO 輸出1 [, …, 輸出N]…];
注意本地動態SQL僅支持弱類型REF CURSOR,即對於REF CURSOR,不支持BULK COLLECT。
16.3 最後說明
動態SQL的負面:破壞了依賴鏈、代碼更脆弱、很難調優。
第 17 章 interMedia