ORACLE的優化器(二)

8. 使用DECODE函數可以避免重複掃描相同記錄或重複連接相同的表. 
例如: 
SELECT COUNT(*),SUM(SAL) 
FROM EMP 
WHERE DEPT_NO = 0020 
AND ENAME LIKE ‘SMITH%'; 

SELECT COUNT(*),SUM(SAL) 
FROM EMP 
WHERE DEPT_NO = 0030 
AND ENAME LIKE ‘SMITH%'; 


你可以用DECODE函數高效地得到相同結果 

SELECT COUNT(DECODE(DEPT_NO,0020,'X',NULL)) D0020_COUNT, 
COUNT(DECODE(DEPT_NO,0030,'X',NULL)) D0030_COUNT, 
SUM(DECODE(DEPT_NO,0020,SAL,NULL)) D0020_SAL, 
SUM(DECODE(DEPT_NO,0030,SAL,NULL)) D0030_SAL 
FROM EMP WHERE ENAME LIKE ‘SMITH%'; 

類似的,DECODE函數也可以運用於GROUP BY 和ORDER BY子句中. 

如果DECODE取值爲NULL,SUM(NULL)的值是NULL -->如果所有的值都是NULL , SUM(NULL) = NULL 但是只要有一個值不是NULL,SUM() <> NULL 所以原SQL應該沒有什麼邏輯上的問題 
menlion (2003-9-4 12:38:01) 
關於第八點的個人看法:如果DECODE取值爲NULL,SUM(NULL)的值是NULL,不會正常求和的。可以改成如下所示就好了: SELECT COUNT(DECODE(DEPT_NO,0020,'X',NULL)) D0020_COUNT, COUNT(DECODE(DEPT_NO,0030,'X',NULL)) D0030_COUNT, SUM(DECODE(DEPT_NO,0020,SAL,0)) D0020_SAL, SUM(DECODE(DEPT_NO,0030,SAL,0)) D0030_SAL FROM EMP 
WHERE ENAME LIKE ‘SMITH%'; 

9. 整合簡單,無關聯的數據庫訪問
如果你有幾個簡單的數據庫查詢語句,你可以把它們整合到一個查詢中(即使它們之間沒有關係) 
例如: 
SELECT NAME FROM EMP 
WHERE EMP_NO = 1234; 

SELECT NAME 
FROM DPT 
WHERE DPT_NO = 10 ; 

SELECT NAME FROM CAT 
WHERE CAT_TYPE = ‘RD'; 

上面的3個查詢可以被合併成一個: 

SELECT E.NAME , D.NAME , C.NAME 
FROM CAT C , DPT D , EMP E,DUAL X 
WHERE NVL(‘X',X.DUMMY) = NVL(‘X',E.ROWID(+)) 
AND NVL(‘X',X.DUMMY) = NVL(‘X',D.ROWID(+)) 
AND NVL(‘X',X.DUMMY) = NVL(‘X',C.ROWID(+)) 
AND E.EMP_NO(+) = 1234 
AND D.DEPT_NO(+) = 10 
AND C.CAT_TYPE(+) = ‘RD'; 
(譯者按: 雖然採取這種方法,效率得到提高,但是程序的可讀性大大降低,所以讀者 還是要權衡之間的利弊)

 

10. 最高效的刪除重複記錄方法 ( 因爲使用了ROWID) 
DELETE FROM EMP E 
WHERE E.ROWID > (SELECT MIN(X.ROWID) 
FROM EMP X 
WHERE X.EMP_NO = E.EMP_NO); 

11. 使用Truncate代替delete
當刪除表中的記錄時,在通常情況下, 回滾段(rollback segments ) 用來存放可以被恢復的信息. 如果你沒有COMMIT事務,ORACLE會將數據恢復到刪除之前的狀態(準確地說是恢復到執行刪除命令之前的狀況) 而當運用TRUNCATE時, 回滾段不再存放任何可被恢復的信息.當命令運行後,數據不能被恢復.因此很少的資源被調用,執行時間也會很短. 
(譯者按: TRUNCATE只在刪除全表適用,TRUNCATE是DDL不是DML)

12. 儘量多使用Commit
只要有可能,在程序中儘量多使用COMMIT, 這樣程序的性能得到提高,需求也會因爲COMMIT所釋放的資源而減少: 
COMMIT所釋放的資源: 
a. 回滾段上用於恢復數據的信息. 
b. 被程序語句獲得的鎖 
c. redo log buffer 中的空間 
d. ORACLE爲管理上述3種資源中的內部花費 
(譯者按: 在使用COMMIT時必須要注意到事務的完整性,現實中效率和事務完整性往往是魚和熊掌不可得兼) 

13. 計算記錄條數
和一般的觀點相反, count(*) 比count(1)稍快 , 當然如果可以通過索引檢索,對索引列的計數仍舊是最快的. 例如 COUNT(EMPNO) 
(譯者按: 在CSDN論壇?曾經對此有過相當熱烈的討論, 作者的觀點並不十分準確,通過實際的測試,上述三種方法並沒有顯著的性能差別) 

14. 使用where子句代替having 
避免使用HAVING子句, HAVING 只會在檢索出所有記錄之後纔對結果集進行過濾. 這個處理需要排序,總計等操作. 如果能通過WHERE子句限制記錄的數目,那就能減少這方面的開銷. 
例如: 
低效: 
SELECT REGION,AVG(LOG_SIZE) 
FROM LOCATION 
GROUP BY REGION 
HAVING REGION REGION != ‘SYDNEY' AND REGION != ‘PERTH' 

高效 
SELECT REGION,AVG(LOG_SIZE) 
FROM LOCATION 
WHERE REGION REGION != ‘SYDNEY' 
AND REGION != ‘PERTH' GROUP BY REGION 

(譯者按: HAVING 中的條件一般用於對一些集合函數的比較,如COUNT() 等等. 除此而外,一般的條件應該寫在WHERE子句中) 

15. 減少對錶的查詢
在含有子查詢的SQL語句中,要特別注意減少對錶的查詢. 
例如: 
低效 
SELECT TAB_NAME 
FROM TABLES 
WHERE TAB_NAME = ( SELECT TAB_NAME 
FROM TAB_COLUMNS 
WHERE VERSION = 604) 
AND DB_VER= ( SELECT DB_VER 
  FROM TAB_COLUMNS 
  WHERE VERSION = 604) 

高效 

SELECT TAB_NAME 
FROM TABLES 
WHERE (TAB_NAME,DB_VER) 
  = ( SELECT TAB_NAME,DB_VER
  FROM TAB_COLUMNS 
  WHERE VERSION = 604) 
Update 多個Column 例子: 
低效: 
UPDATE EMP 
SET EMP_CAT = (SELECT MAX(CATEGORY) FROM EMP_CATEGORIES), 
SAL_RANGE = (SELECT MAX(SAL_RANGE) FROM EMP_CATEGORIES) 
WHERE EMP_DEPT = 0020; 

高效: 
UPDATE EMP 
SET (EMP_CAT, SAL_RANGE) 
= (SELECT MAX(CATEGORY) , MAX(SAL_RANGE) 
FROM EMP_CATEGORIES) 
WHERE EMP_DEPT = 0020; 

16. 通過內部函數提高SQL效率
SELECT H.EMPNO,E.ENAME,H.HIST_TYPE,T.TYPE_DESC,COUNT(*) 
FROM HISTORY_TYPE T,EMP E,EMP_HISTORY H 
WHERE H.EMPNO = E.EMPNO 
AND H.HIST_TYPE = T.HIST_TYPE 
GROUP BY H.EMPNO,E.ENAME,H.HIST_TYPE,T.TYPE_DESC; 
通過調用下面的函數可以提高效率. 
FUNCTION LOOKUP_HIST_TYPE(TYP IN NUMBER) RETURN VARCHAR2 
AS 
TDESC VARCHAR2(30); 
CURSOR C1 IS 
SELECT TYPE_DESC 
FROM HISTORY_TYPE 
WHERE HIST_TYPE = TYP; 
BEGIN 
OPEN C1; 
FETCH C1 INTO TDESC; 
CLOSE C1; 
RETURN (NVL(TDESC,'?')); 
END; 

FUNCTION LOOKUP_EMP(EMP IN NUMBER) RETURN VARCHAR2 
AS 
ENAME VARCHAR2(30); 
CURSOR C1 IS 
SELECT ENAME 
FROM EMP 
WHERE EMPNO=EMP; 
BEGIN 
OPEN C1; 
FETCH C1 INTO ENAME; 
CLOSE C1; 
RETURN (NVL(ENAME,'?')); 
END; 

SELECT H.EMPNO,LOOKUP_EMP(H.EMPNO), 
H.HIST_TYPE,LOOKUP_HIST_TYPE(H.HIST_TYPE),COUNT(*) 
FROM EMP_HISTORY H 
GROUP BY H.EMPNO , H.HIST_TYPE; 
(譯者按: 經常在論壇中看到如 '能不能用一個SQL寫出….' 的貼子, 殊不知複雜的SQL往往犧牲了執行效率. 能夠掌握上面的運用函數解決問題的方法在實際工作中是非常有意義的)


17. 使用表的別名
當在SQL語句中連接多個表時, 請使用表的別名並把別名前綴於每個Column上.這樣一來,就可以減少解析的時間並減少那些由Column歧義引起的語法錯誤. 
(譯者注: Column歧義指的是由於SQL中不同的表具有相同的Column名,當SQL語句中出現這個Column時,SQL解析器無法判斷這個Column的歸屬) 

18. 使用Exists代替in
在許多基於基礎表的查詢中,爲了滿足一個條件,往往需要對另一個表進行聯接.在這種情況下, 使用EXISTS(或NOT EXISTS)通常將提高查詢的效率. 
低效: 
SELECT * FROM EMP (基礎表) 
WHERE EMPNO > 0 
AND DEPTNO IN (SELECT DEPTNO 
  FROM DEPT 
  WHERE LOC = ‘MELB') 

高效: 
SELECT * FROM EMP (基礎表) 
WHERE EMPNO > 0 
AND EXISTS (SELECT ‘X' 
  FROM DEPT 
  WHERE DEPT.DEPTNO = EMP.DEPTNO 
  AND LOC = ‘MELB') 
(譯者按: 相對來說,用NOT EXISTS替換NOT IN 將更顯著地提高效率,下一節中將指出)

19. 使用not exists代替not in
在子查詢中,NOT IN子句將執行一個內部的排序和合並. 無論在哪種情況下,NOT IN都是最低效的 (因爲它對子查詢中的表執行了一個全表遍歷). 避免使用NOT IN ,我們可以把它改寫成外連接(Outer Joins)或NOT EXISTS. 
例如: 
SELECT … FROM EMP 
WHERE DEPT_NO NOT IN (SELECT DEPT_NO 
  FROM DEPT 
  WHERE DEPT_CAT='A'); 

爲了提高效率.改寫爲: 

(方法一: 高效) 
SELECT …. 
FROM EMP A,DEPT B 
WHERE A.DEPT_NO = B.DEPT(+) 
AND B.DEPT_NO IS NULL 
AND B.DEPT_CAT(+) = ‘A' 

(方法二: 最高效) 
SELECT …. 
FROM EMP E 
WHERE NOT EXISTS (SELECT ‘X' 
FROM DEPT D 
WHERE D.DEPT_NO = E.DEPT_NO 
AND DEPT_CAT = ‘A'); 

20. 使用表連接替換exists
通常來說 , 採用表連接的方式比EXISTS更有效率 
SELECT ENAME FROM EMP E 
WHERE EXISTS (SELECT ‘X' 
FROM DEPT 
WHERE DEPT_NO = E.DEPT_NO 
AND DEPT_CAT = ‘A'); 

(更高效) 
SELECT ENAME FROM DEPT D,EMP E 
WHERE E.DEPT_NO = D.DEPT_NO 
AND DEPT_CAT = ‘A' ; 
(譯者按: 在RBO的情況下,前者的執行路徑包括FILTER,後者使用NESTED LOOP) 


21. 使用exists替換distinct
當提交一個包含一對多表信息(比如部門表和僱員表)的查詢時,避免在SELECT子句中使用DISTINCT. 一般可以考慮用EXIST替換 
例如: 
低效: 
SELECT DISTINCT DEPT_NO,DEPT_NAME FROM DEPT D,EMP E 
WHERE D.DEPT_NO = E.DEPT_NO 
高效: 
SELECT DEPT_NO,DEPT_NAME FROM DEPT D 
WHERE EXISTS ( SELECT ‘X' 
FROM EMP E 
WHERE E.DEPT_NO = D.DEPT_NO); 
EXISTS 使查詢更爲迅速,因爲RDBMS核心模塊將在子查詢的條件一旦滿足後,立刻返回結果. 


22. 用下列SQL工具找出低效SQL: 
SELECT EXECUTIONS , DISK_READS, BUFFER_GETS, 
ROUND((BUFFER_GETS-DISK_READS)/BUFFER_GETS,2) Hit_radio, 
ROUND(DISK_READS/EXECUTIONS,2) Reads_per_run, 
SQL_TEXT 
FROM V$SQLAREA 
WHERE EXECUTIONS>0 
AND BUFFER_GETS > 0 
AND (BUFFER_GETS-DISK_READS)/BUFFER_GETS < 0.8 
ORDER BY 4 DESC; 
(譯者按: 雖然目前各種關於SQL優化的圖形化工具層出不窮,但是寫出自己的SQL工具來解決問題始終是一個最好的方法) 

 

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