普通物理表、臨時表和TABLE的用途各不相同,普通表一般存放需要長期保存的數據,臨時表存放某個事務或會話過程中的臨時數據,array是oracle中的數組,也可以用於存放臨時數據,或在OODB中使用。但是,優化器在做查詢計劃時,尤其在CBO中,沒臨時表和TABLE的統計數據,因此總會得出一些混亂的查詢計劃,往往需要我們用hint去調整。並且,由於實現機制各不相同,他們的查詢效率也不一樣。下面通過一個簡單的測試來對比他們之間的查詢效率,以便於在可選的情況下選擇最優的實現方式:
創建相關對象:
創建物理表:
CREATE TABLE tmp_obj (
OWNER VARCHAR2(30),
TABLE_NAME VARCHAR2(20)
);
給物理表插入測試數據:
BEGIN
FOR i IN 1..90000 LOOP
INSERT INTO tmp_obj VALUES(i, i);
END LOOP;
COMMIT;
END;
/
創建臨時表:
CREATE GLOBAL TEMPORARY TABLE TMP_OBJ
(
OWNER VARCHAR2(30),
TABLE_NAME VARCHAR2(20)
)
ON COMMIT PRESERVE ROWS;
創建table類型:
CREATE OR REPLACE TYPE TY_OBJ
IS OBJECT (
OWNER VARCHAR2(30),
TABLE_NAME VARCHAR2(20))
/
CREATE OR REPLACE TYPE TY_OBJ_LST IS TABLE OF TY_OBJ
/
創建一張記錄統計數據的表:
CREATE TABLE stat_tables (tid number, idate date, tmptime number,
funtime number, phytime number);
CREATE SEQUENCE stat_id_seq START WITH 1;
創建一個函數來測試他們的查詢效率:
CREATE OR REPLACE PROCEDURE P_TESTTABLE IS
v_objtab TY_OBJ_LST;
v_bdate TIMESTAMP;
v_edate TIMESTAMP;
v_number NUMBER;
v_tmptime NUMBER;
v_funtime NUMBER;
v_phytime NUMBER;
BEGIN
v_objtab := TY_OBJ_LST();
DELETE FROM tmp_obj;
FOR i IN 1..90000 LOOP
INSERT INTO tmp_obj VALUES(i, i);
v_objtab.EXTEND;
v_objtab(i) := ty_obj(to_char(i), to_char(i));
END LOOP;
FOR i IN 1..100 LOOP
dbms_output.put_line('');
dbms_output.put_line('select from temp table ...');
v_bdate := current_timestamp();
-- dbms_output.put_line(v_bdate);
SELECT COUNT(*) INTO v_number FROM tmp_obj;
-- dbms_output.put_line(v_number || ' rows selected!');
v_edate := current_timestamp();
-- dbms_output.put_line(v_edate);
v_tmptime := extract( day from (v_edate-v_bdate) )*24*60*60+
extract( hour from (v_edate-v_bdate) )*60*60+
extract( minute from (v_edate-v_bdate) )*60+
extract( second from (v_edate-v_bdate));
dbms_output.put_line('consumed: '||to_char(v_tmptime)||' seconds');
dbms_output.put_line('');
dbms_output.put_line('select from function table ...');
v_bdate := current_timestamp();
-- dbms_output.put_line(v_bdate);
SELECT COUNT(*) INTO v_number FROM TABLE(CAST(v_objtab AS TY_OBJ_LST));
-- dbms_output.put_line(v_number || ' rows selected!');
v_edate := current_timestamp();
-- dbms_output.put_line(v_edate);
v_funtime := extract( day from (v_edate-v_bdate) )*24*60*60+
extract( hour from (v_edate-v_bdate) )*60*60+
extract( minute from (v_edate-v_bdate) )*60+
extract( second from (v_edate-v_bdate));
dbms_output.put_line('consumed: '||to_char(v_funtime)||' seconds');
dbms_output.put_line('');
dbms_output.put_line('select from physical table ...');
v_bdate := current_timestamp();
-- dbms_output.put_line(v_bdate);
SELECT COUNT(*) INTO v_number FROM phy_obj;
-- dbms_output.put_line(v_number || ' rows selected!');
v_edate := current_timestamp();
-- dbms_output.put_line(v_edate);
v_phytime := extract( day from (v_edate-v_bdate) )*24*60*60+
extract( hour from (v_edate-v_bdate) )*60*60+
extract( minute from (v_edate-v_bdate) )*60+
extract( second from (v_edate-v_bdate));
dbms_output.put_line('consumed: '||to_char(v_phytime)||' seconds');
INSERT INTO stat_tables (tid , idate , tmptime , funtime , phytime )
VALUES (stat_id_seq.nextval, SYSDATE, v_tmptime, v_funtime, v_phytime);
END LOOP;
COMMIT;
END P_TESTTABLE;
執行函數,得出他們的查詢時間的統計數據:
SET SERVEROUTPUT ON SIZE 50000
EXEC P_TESTTABLE;
然後利用PLSQL developer 7.0的直方圖嚮導從統計表stat_tables中來生成數據對比的直方圖,做一個直觀的對比:
從圖上可以看出,平均效率最差的是TABLE,它與其他兩者對比效率相差幾個數量級,這個數據庫中使一個不可忽視的效率差異。其次爲臨時表,而普通物理表的效率最優。而第一次訪問效率則是TABLE最好。
我們來簡單瞭解一下他們各自實現機制,以便於理解爲什麼他們之間會存在這個效率差異。
對於物理表,它的數據是存儲在物理磁盤上的。但數據第一次read時,會被load到db cache中去。根據LRU算法來決定這些數據是否會被置換出內存(如果建表時使用了cache,則會一直存儲在內存中)。我們這主要是考慮這些數據在內存中被掃描。由於它的數據是會被所有會話共享的,所以就會存在鎖和髒數據的問題。而由於它的數據是結構化的,所以在內存中進行數據掃描時,效率是最高的。
對於臨時表,它及它的索引都是創建在臨時表空間上的。當在一個會話中第一次插入數據時,纔開始在用戶的默認臨時表空間給他分配臨時段,不同的會話會分配不同的臨時段。這就決定的了各個會話的同一個臨時表的數據不會相互影響。臨時表的數據也是結構化的。第一次讀取數據後,數據也被cache到db cache中。
此外,在建表時可以通過參數指定它是事物級的還是會話級的,但是對查詢效率來說是沒什麼影響的。還有一點,臨時表的數據變化是不會產生redo log的(但會產生undo log),當然,這對查詢效率也沒有影響,致使順帶提一下。
對於TABLE函數(它是9i中的新特性),實際上是將一個存儲在內存中的對象(以流的方式存儲的)結構化以後,使這個對象能以表的方式查詢。他對流對象的結構化轉換就決定的它的效率大大遜於對普通表和臨時表的查詢效率。