Oracle 優化詳解——來自網路

ForTechnology發表於2011-08-04
Normal 0 7.8 磅 0 2 false false false MicrosoftInternetExplorer4 Oracle sql 效能優化調整 收藏

 

                    Oracle  sql 效能優化調整
1.
選用適合的ORACLE優化
    
ORACLE優化器共有3:
    a.  RULE (
基於規則)   b. COST (基於成本)  c. CHOOSE (選擇性)
    
設定預設的優化,可以通過對init.ora檔案中OPTIMIZER_MODE引數的各種宣告,RULE,COST,CHOOSE,ALL_ROWS,FIRST_ROWS . 你當然也在SQL句級或是會話(session)級對其進行覆蓋.
   
為了使用基於成本的優化(CBO, Cost-Based Optimizer) , 你必須經常執行analyze 命令,以增加資料庫中的物件統計資訊(object statistics)的準確性.
   
如果資料庫的優化器模式設定為選擇性(CHOOSE),那麼實際的優化器模式將和是否執行過analyze命令有關. 如果table已經被analyze, 優化器模式將自動成為CBO , 反之,資料庫將採用RULE形式的優化.
   
在預設情況下,ORACLE採用CHOOSE優化, 為了避免那些不必要的全表掃描(full table scan) , 你必須儘量避免使用CHOOSE優化,而直接採用基於規則或者基於成本的優化.
   2.      
訪問Table的方式
  
ORACLE 採用兩種訪問表中記錄的方式:
a.      
全表掃描 
            
全表掃描就是順序地訪問表中每條記錄. ORACLE採用一次讀入多個資料塊(database block)的方式優化全表掃描.
     b.      
通過ROWID訪問表
       
你可以採用基於ROWID的訪問方式情況,提高訪問表的效率, , ROWID包含了表中記錄的物理位置資訊..ORACLE採用索引(INDEX)實現了資料和存放資料的物理位置(ROWID)之間的聯絡. 通常索引提供了快速訪問ROWID的方法,因此那些基於索引列的查詢就可以得到效能上的提高.
  3.      
共享SQL語句
為了不重複解析相同的SQL語句,在第一次解析之後, ORACLESQL語句存放在記憶體中.這塊位於系統全域性區域SGA(system global area)的共享池(shared buffer pool)中的記憶體可以被所有的資料庫使用者共享. 因此,當你執行一個SQL語句(有時被稱為一個遊標),如果它
和之前的執行過的語句完全相同, ORACLE就能很快獲得已經被解析的語句以及最好的
執行路徑. ORACLE的這個功能大大地提高了SQL的執行效能並節省了記憶體的使用.
     
可惜的是ORACLE只對簡單的表提供高速緩衝(cache buffering) ,這個功能並不適用於多表連線查詢.
資料庫管理員必須在init.ora中為這個區域設定合適的引數,當這個記憶體區域越大,就可以保留更多的語句,當然被共享的可能性也就越大了.
當你向ORACLE 提交一個SQL語句,ORACLE會首先在這塊記憶體中查詢相同的語句.
 
這裡需要註明的是,ORACLE對兩者採取的是一種嚴格匹配,要達成共享,SQL語句必須
完全相同(包括空格,換行等).
     
共享的語句必須滿足三個條件:
  A.     
字元級的比較:
當前被執行的語句和共享池中的語句必須完全相同.
      
例如:
           SELECT * FROM EMP;
      
和下列每一個都不同
           SELECT * from EMP;
           Select * From Emp;
           SELECT      *     FROM EMP;
B.     
兩個語句所指的物件必須完全相同:
例如:
   
使用者  物件名    如何訪問
Jack  sal_limit    private synonym
     Work_city  public synonym
     Plant_detail  public synonym
Jill    sal_limit    private synonym
     Work_city  public synonym
     Plant_detail  table owner
     
考慮一下下列SQL語句能否在這兩個使用者之間共享.
  SQL 
能否共享  原因
select max(sal_cap) from sal_limit; 
不能  每個使用者都有一個private synonym - sal_limit , 它們是不同的物件
select count(*0 from work_city where sdesc like 'NEW%'; 
  兩個使用者訪問相同的物件public synonym - work_city 
select a.sdesc,b.location from work_city a , plant_detail b where a.city_id = b.city_id 
不能  使用者jack 通過private synonym訪問plant_detail jill 是表的所有者,物件不同.
  C.     
兩個SQL語句中必須使用相同的名字的繫結變數(bind variables)
例如:第一組的兩個SQL語句是相同的(可以共享),而第二組中的兩個語句是不同的(即使在執行時,賦於不同的繫結變數相同的值)
a.
select pin , name from people where pin = :blk1.pin;
select pin , name from people where pin = :blk1.pin;
b.
select pin , name from people where pin = :blk1.ot_ind;
select pin , name from people where pin = :blk1.ov_ind;
4.
選擇最有效率的表名順序(只在基於規則的優化器中有效)
ORACLE的解析器按照從右到左的順序處理FROM子句中的表名,因此FROM子句中寫在最後的表(基礎表 driving table)將被最先處理. FROM子句中包含多個表的情況下,你必須選擇記錄條數最少的表作為基礎表.ORACLE處理多個表時, 會運用排序及合併的方式連線它們.首先,掃描第一個表(FROM子句中最後的那個表)並對記錄進行派序,然後掃描第二個表(FROM子句中最後第二個表),最後將所有從第二個表中檢索出的記錄與第一個表中合適記錄進行合併.
例如:      TAB1 16,384 條記錄
         
TAB2 1      條記錄
     
選擇TAB2作為基礎表 (最好的方法)
      select count(*) from tab1,tab2  
執行時間0.96
     
選擇TAB2作為基礎表 (不佳的方法)
      select count(*) from tab2,tab1  
執行時間26.09
如果有3個以上的表連線查詢, 那就需要選擇交叉表(intersection table)作為基礎表, 交叉表是指那個被其他表所引用的表.
例如:   EMP表描述了LOCATION表和CATEGORY表的交集.
SELECT * 
FROM LOCATION L , 
       CATEGORY C,
       EMP E 
WHERE E.EMP_NO BETWEEN 1000 AND 2000
AND E.CAT_NO = C.CAT_NO
AND E.LOCN = L.LOCN
 
將比下列SQL更有效率
SELECT * 
FROM EMP E ,
LOCATION L , 
       CATEGORY C
WHERE  E.CAT_NO = C.CAT_NO
AND E.LOCN = L.LOCN
AND E.EMP_NO BETWEEN 1000 AND 2000
5.       WHERE
子句中的連線順序.
   
ORACLE採用自下而上的順序解析WHERE子句,根據這個原理,表之間的連線必須寫在其他WHERE條件之前, 那些可以過濾掉最大數量記錄的條件必須寫在WHERE子句的末尾.
 
例如:
  (
低效,執行時間156.3)
SELECT … 
FROM EMP E
WHERE  SAL > 50000
AND    JOB = ‘MANAGER’
AND    25 < (SELECT COUNT(*) FROM EMP
              WHERE MGR=E.EMPNO);
  (
高效,執行時間10.6)
SELECT … 
FROM EMP E
WHERE 25 < (SELECT COUNT(*) FROM EMP
              WHERE MGR=E.EMPNO)
AND    SAL > 50000
AND    JOB = ‘MANAGER’;
6.     SELECT
子句中避免使用 ‘ * ‘
當你想在SELECT子句中列出所有的COLUMN,使用動態SQL列引用 ‘*’ 是一個方便的方法.不幸的是,這是一個非常低效的方法. 實際上,ORACLE在解析的過程中, 會將’*’ 依次轉換成所有的列名, 這個工作是通過查詢資料字典完成的, 這意味著將耗費更多的時間
7.    
減少訪問資料庫的次數
當執行每條SQL語句時, ORACLE在內部執行了許多工作: 解析SQL語句, 估算索引的利用率, 繫結變數 , 讀資料塊等等. 由此可見, 減少訪問資料庫的次數 , 就能實際上減少ORACLE的工作量.
 
例如,
    
以下有三種方法可以檢索出僱員號等於03420291的職員.
 
方法1 (最低效)
     SELECT EMP_NAME , SALARY , GRADE
     FROM EMP 
     WHERE EMP_NO = 342;
      SELECT EMP_NAME , SALARY , GRADE
     FROM EMP 
     WHERE EMP_NO = 291;
方法2 (次低效)
        DECLARE 
         CURSOR C1 (E_NO NUMBER) IS 
         SELECT EMP_NAME,SALARY,GRADE
         FROM EMP 
         WHERE EMP_NO = E_NO;
     BEGIN 
         OPEN C1(342);
         FETCH C1 INTO …,..,.. ;
                 OPEN C1(291);
        FETCH C1 INTO …,..,.. ;
          CLOSE C1;
       END;
方法3 (高效)
     SELECT A.EMP_NAME , A.SALARY , A.GRADE,
             B.EMP_NAME , B.SALARY , B.GRADE
     FROM EMP A,EMP B
     WHERE A.EMP_NO = 342
     AND   B.EMP_NO = 291;
 
注意:
SQL*Plus , SQL*FormsPro*C中重新設定ARRAYSIZE引數, 可以增加每次資料庫訪問的檢索資料量 ,建議值為200.
8.      
使用DECODE函式來減少處理時間
使用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子句中.
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只在刪除全表適用,TRUNCATEDDL不是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.      
使用表的別名(Alias)
當在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工具找出低效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工具來解決問題始終是一個最好的方法)
  23.      
使用TKPROF 工具來查詢SQL效能狀態
  SQL trace
工具收集正在執行的SQL的效能狀態資料並記錄到一個跟蹤檔案中. 這個跟蹤檔案提供了許多有用的資訊,例如解析次數.執行次數,CPU使用時間等.這些資料將可以用來優化你的系統.
設定SQL TRACE在會話級別: 有效
    ALTER SESSION SET SQL_TRACE TRUE
設定SQL TRACE 在整個資料庫有效仿, 你必須將SQL_TRACE引數在init.ora中設為TRUE, USER_DUMP_DEST引數說明了生成跟蹤檔案的目錄
   (
譯者按: 這一節中,作者並沒有提到TKPROF的用法, SQL TRACE的用法也不夠準確, 設定SQL TRACE首先要在init.ora中設定TIMED_STATISTICS, 這樣才能得到那些重要的時間狀態. 生成的trace檔案是不可讀的,所以要用TKPROF工具對其進行轉換,TKPROF有許多執行引數. 大家可以參考ORACLE手冊來了解具體的配置. )
24.      
EXPLAIN PLAN 分析SQL語句
  EXPLAIN PLAN
是一個很好的分析SQL語句的工具,它甚至可以在不執行SQL的情況下分析語句. 通過分析,我們就可以知道ORACLE是怎麼樣連線表,使用什麼方式掃描表(索引掃描或全表掃描)以及使用到的索引名稱.
你需要按照從裡到外,從上到下的次序解讀分析的結果. EXPLAIN PLAN分析的結果是用縮排的格式排列的, 最內部的操作將被最先解讀, 如果兩個操作處於同一層中,帶有最小操作號的將被首先執行.
NESTED LOOP
是少數不按照上述規則處理的操作, 正確的執行路徑是檢查對NESTED LOOP提供資料的操作,其中操作號最小的將被最先處理.
譯者按
 
通過實踐, 感到還是用SQLPLUS中的SET TRACE 功能比較方便.
舉例:
  SQL> list
   1  SELECT *
   2  FROM dept, emp
   3* WHERE emp.deptno = dept.deptno
SQL> set autotrace traceonly /*traceonly
可以不顯示執行結果*/
SQL> /
14 rows selected.
Execution Plan
----------------------------------------------------------
    0      SELECT STATEMENT ptimizer=CHOOSE
    1    0   NESTED LOOPS
    2    1     TABLE ACCESS (FULL) OF 'EMP' 
    3    1     TABLE ACCESS (BY INDEX ROWID) OF 'DEPT'
    4    3       INDEX (UNIQUE SCAN) OF 'PK_DEPT' (UNIQUE)
  
Statistics
----------------------------------------------------------
           0  recursive calls
           2  db block gets
          30  consistent gets
           0  physical reads
           0  redo size
        2598  bytes sent via SQL*Net to client
         503  bytes received via SQL*Net from client
           2  SQL*Net roundtrips to/from client
           0  sorts (memory)
           0  sorts (disk)
          14  rows processed
通過以上分析,可以得出實際的執行步驟是:
1.       TABLE ACCESS (FULL) OF 'EMP' 
2.       INDEX (UNIQUE SCAN) OF 'PK_DEPT' (UNIQUE)
3.       TABLE ACCESS (BY INDEX ROWID) OF 'DEPT'
4.       NESTED LOOPS (JOINING 1 AND 3)
: 目前許多第三方的工具如TOADORACLE本身提供的工具如OMSSQL Analyze都提供了極其方便的EXPLAIN PLAN工具.也許喜歡圖形化介面的朋友們可以選用它們.
25.      
用索引提高效率
 
索引是表的一個概念部分,用來提高檢索資料的效率. 實際上,ORACLE使用了一個複雜的自平衡B-tree結構. 通常,通過索引查詢資料比全表掃描要快. ORACLE找出執行查詢和Update語句的最佳路徑時, ORACLE優化器將使用索引. 同樣在聯結多個表時使用索引也可以提高效率. 另一個使用索引的好處是,它提供了主鍵(primary key)的唯一性驗證.
除了那些LONGLONG RAW資料型別, 你可以索引幾乎所有的列. 通常, 在大型表中使用索引特別有效. 當然,你也會發現, 在掃描小表時,使用索引同樣能提高效率.
雖然使用索引能得到查詢效率的提高,但是我們也必須注意到它的代價. 索引需要空間來
,也需要定期維護, 每當有記錄在表中增減或索引列被修改時, 索引本身也會被修改. 這意味著每條記錄的INSERT , DELETE , UPDATE將為此多付出4 , 5 次的磁碟I/O . 因為索引需要額外的儲存空間和處理,那些不必要的索引反而會使查詢反應時間變慢.
 
譯者按
定期的重構索引是有必要的
ALTER INDEX REBUILD
 
26.      
索引的操作
 
ORACLE對索引有兩種訪問模式.
 
索引唯一掃描 ( INDEX UNIQUE SCAN)
 
大多數情況下, 優化器通過WHERE子句訪問INDEX.
 
例如:
LODGING有兩個索引 : 建立在LODGING列上的唯一性索引LODGING_PK和建立在MANAGER列上的非唯一性索引LODGING$MANAGER. 
 
SELECT * 
FROM LODGING
WHERE LODGING = ‘ROSE HILL’;
 
   
在內部 , 上述SQL將被分成兩步執行, 首先 , LODGING_PK 索引將通過索引唯一掃描的方式被訪問 , 獲得相對應的ROWID, 通過ROWID訪問表的方式 執行下一步檢索.
   
如果被檢索返回的列包括在INDEX列中,ORACLE將不執行第二步的處理(通過ROWID訪問表). 因為檢索資料儲存在索引中, 單單訪問索引就可以完全滿足查詢結果
   
下面SQL只需要INDEX UNIQUE SCAN 操作.
         
         SELECT LODGING
         FROM  LODGING
WHERE LODGING = ‘ROSE HILL’;
 
  
索引範圍查詢(INDEX RANGE SCAN)
      
適用於兩種情況:
1.      
基於一個範圍的檢索
2.      
基於非唯一性索引的檢索
 
 
1:
 
       SELECT LODGING
       FROM  LODGING
WHERE LODGING LIKE ‘M%’;
 
WHERE
子句條件包括一系列值, ORACLE將通過索引範圍查詢的方式查詢LODGING_PK . 由於索引範圍查詢將返回一組值, 它的效率就要比索引唯一掃描
低一些
 
2:
 
       SELECT LODGING
       FROM  LODGING
WHERE MANAGER = ‘BILL GATES’;
  
  
這個SQL的執行分兩步, LODGING$MANAGER的索引範圍查詢(得到所有符合條件記錄的ROWID) 和下一步同過ROWID訪問表得到LODGING列的值. 由於LODGING$MANAGER是一個非唯一性的索引,資料庫不能對它執行索引唯一掃描
 
  
由於SQL返回LODGING,而它並不存在於LODGING$MANAGER索引中, 所以在索引範圍查詢後會執行一個通過ROWID訪問表的操作
 
   WHERE
子句中, 如果索引列所對應的值的第一個字元由萬用字元(WILDCARD)開始, 索引將不被採用.
 
  SELECT LODGING
       FROM  LODGING
WHERE MANAGER LIKE ‘
HANMAN’;
 
 
在這種情況下,ORACLE將使用全表掃描.
27.      
基礎表的選擇
 
基礎表(Driving Table)是指被最先訪問的表(通常以全表掃描的方式被訪問). 根據優化器的不同, SQL語句中基礎表的選擇是不一樣的.
如果你使用的是CBO (COST BASED OPTIMIZER),優化器會檢查SQL語句中的每個表的物理大小,索引的狀態,然後選用花費最低的執行路徑.
如果你用RBO (RULE BASED OPTIMIZER) , 並且所有的連線條件都有索引對應, 在這種情況下, 基礎表就是FROM 子句中列在最後的那個表.
舉例:
      SELECT A.NAME , B.MANAGER
      FROM
 WORKER A, 
              LODGING B
      WHERE
 A.LODGING = B.LODING;
由於LODGING表的LODING列上有一個索引, 而且WORKER表中沒有相比較的索引, WORKER表將被作為查詢中的基礎表.
 
28.      
多個平等的索引
SQL語句的執行路徑可以使用分佈在多個表上的多個索引時, ORACLE會同時使用多個索引並在執行時對它們的記錄進行合併, 檢索出僅對全部索引有效的記錄.
ORACLE選擇執行路徑時,唯一性索引的等級高於非唯一性索引. 然而這個規則只有
WHERE子句中索引列和常量比較才有效.如果索引列和其他表的索引類相比較. 這種子句在優化器中的等級是非常低的.
如果不同表中兩個想同等級的索引將被引用, FROM子句中表的順序將決定哪個會被率先使用. FROM子句中最後的表的索引將有最高的優先順序.
如果相同表中兩個想同等級的索引將被引用, WHERE子句中最先被引用的索引將有最高的優先順序.
舉例:
      DEPTNO
上有一個非唯一性索引,EMP_CAT也有一個非唯一性索引.
      SELECT ENAME,
      FROM EMP
      WHERE DEPT_NO = 20
      AND EMP_CAT = ‘A’;
這裡,DEPTNO索引將被最先檢索,然後同EMP_CAT索引檢索出的記錄進行合併. 執行路徑如下:
 
TABLE ACCESS BY ROWID ON EMP
     AND-EQUAL
         INDEX RANGE SCAN ON DEPT_IDX
         INDEX RANGE SCAN ON CAT_IDX
 
29.       
等式比較和範圍比較
     
WHERE子句中有索引列, ORACLE不能合併它們,ORACLE將用範圍比較.
 
     
舉例:
      DEPTNO
上有一個非唯一性索引,EMP_CAT也有一個非唯一性索引.
      SELECT ENAME
      FROM EMP
      WHERE DEPTNO > 20
      AND EMP_CAT = ‘A’;
     
     
這裡只有EMP_CAT索引被用到,然後所有的記錄將逐條與DEPTNO條件進行比較. 執行路徑如下:
      TABLE ACCESS BY ROWID ON EMP 
            INDEX RANGE SCAN ON CAT_IDX
 
30.      
不明確的索引等級
 
ORACLE無法判斷索引的等級高低差別,優化器將只使用一個索引,它就是在WHERE子句中被列在最前面的.
     
舉例:
      DEPTNO
上有一個非唯一性索引,EMP_CAT也有一個非唯一性索引.
      
      SELECT ENAME
      FROM EMP
      WHERE DEPTNO > 20
      AND EMP_CAT > ‘A’;
 
     
這裡, ORACLE只用到了DEPT_NO索引. 執行路徑如下:
      
      TABLE ACCESS BY ROWID ON EMP
           INDEX RANGE SCAN ON DEPT_IDX
 
譯者按:
我們來試一下以下這種情況:
SQL> select index_name, uniqueness from user_indexes where table_name = 'EMP';
 
INDEX_NAME                     UNIQUENES
------------------------------ ---------
EMPNO                          UNIQUE
EMPTYPE                        NONUNIQUE
 
SQL> select * from emp where empno >= 2 and emp_type = 'A' ;
 
no rows selected
 
Execution Plan
----------------------------------------------------------
    0      SELECT STATEMENT ptimizer=CHOOSE
    1    0   TABLE ACCESS (BY INDEX ROWID) OF 'EMP'
    2    1     INDEX (RANGE SCAN) OF 'EMPTYPE' (NON-UNIQUE)
  
雖然EMPNO是唯一性索引,但是由於它所做的是範圍比較, 等級要比非唯一性索引的等式比較低!
31.      
強制索引失效
 
    
如果兩個或以上索引具有相同的等級,你可以強制命令ORACLE優化器使用其中的一個(通過它,檢索出的記錄數量少) .
 
舉例:
    
SELECT ENAME
FROM EMP
WHERE EMPNO = 7935  
AND DEPTNO + 0 = 10    /*DEPTNO
上的索引將失效*/
AND EMP_TYPE || ‘’ = ‘A’  /*EMP_TYPE
上的索引將失效*/
 
這是一種相當直接的提高查詢效率的辦法. 但是你必須謹慎考慮這種策略,一般來說,只有在你希望單獨優化幾個SQL時才能採用它.
 
這裡有一個例子關於何時採用這種策略
 
假設在EMP表的EMP_TYPE列上有一個非唯一性的索引而EMP_CLASS上沒有索引
 
SELECT ENAME
FROM EMP
WHERE EMP_TYPE = ‘A’ 
AND EMP_CLASS = ‘X’;
 
優化器會注意到EMP_TYPE上的索引並使用它. 這是目前唯一的選擇. 如果,一段時間以後, 另一個非唯一性建立在EMP_CLASS,優化器必須對兩個索引進行選擇,在通常情況下,優化器將使用兩個索引並在他們的結果集合上執行排序及合併. 然而,如果其中一個索引(EMP_TYPE)接近於唯一性而另一個索引(EMP_CLASS)上有幾千個重複的值. 排序及合併就會成為一種不必要的負擔. 在這種情況下,你希望使優化器遮蔽掉EMP_CLASS索引.
用下面的方案就可以解決問題.
SELECT ENAME
FROM EMP
WHERE EMP_TYPE = ‘A’ 
AND EMP_CLASS||’’ = ‘X’;
 
32.      
避免在索引列上使用計算.
WHERE
子句中,如果索引列是函式的一部分.優化器將不使用索引而使用全表掃描.
 
舉例:
 
低效:
SELECT …
FROM DEPT
WHERE SAL * 12 > 25000;
 
高效:
SELECT …
FROM DEPT
WHERE SAL  > 25000/12;
 
譯者按:
這是一個非常實用的規則,請務必牢記
 
33.      
自動選擇索引
如果表中有兩個以上(包括兩個)索引,其中有一個唯一性索引,而其他是非唯一性.
在這種情況下,ORACLE將使用唯一性索引而完全忽略非唯一性索引.
 
舉例:
SELECT ENAME
FROM EMP
WHERE EMPNO = 2326  
AND DEPTNO  = 20 ;
 
這裡,只有EMPNO上的索引是唯一性的,所以EMPNO索引將用來檢索記錄.
TABLE ACCESS BY ROWID ON EMP
        INDEX UNIQUE SCAN ON EMP_NO_IDX
  
34.      
避免在索引列上使用NOT
通常, 我們要避免在索引列上使用NOT, NOT會產生在和在索引列上使用函式相同的
影響. ORACLE遇到”NOT,他就會停止使用索引轉而執行全表掃描.
   
舉例:
 
   
低效: (這裡,不使用索引)
 
    SELECT …
    FROM DEPT
    WHERE DEPT_CODE NOT = 0;
    
   
高效: (這裡,使用了索引)
 
   SELECT …
    FROM DEPT
    WHERE DEPT_CODE > 0;
 
   
需要注意的是,在某些時候, ORACLE優化器會自動將NOT轉化成相對應的關係操作符.
    NOT >  to  <=
    NOT >=  to  <
    NOT =
    NOT <=  to  >
  
 
譯者按:
     
在這個例子中,作者犯了一些錯誤. 例子中的低效率SQL是不能被執行的.
我做了一些測試:
      
SQL> select * from emp where NOT empno > 1;
no rows selected
Execution Plan
----------------------------------------------------------
    0      SELECT STATEMENT ptimizer=CHOOSE
    1    0   TABLE ACCESS (BY INDEX ROWID) OF 'EMP'
    2    1     INDEX (RANGE SCAN) OF 'EMPNO' (UNIQUE)     
 
SQL> select * from emp where empno <= 1;
no rows selected
Execution Plan
----------------------------------------------------------
    0      SELECT STATEMENT ptimizer=CHOOSE
    1    0   TABLE ACCESS (BY INDEX ROWID) OF 'EMP'
    2    1     INDEX (RANGE SCAN) OF 'EMPNO' (UNIQUE)
 
      
兩者的效率完全一樣,也許這符合作者關於在某些時候, ORACLE優化器會自動將NOT轉化成相對應的關係操作符的觀點.
       
35.      
>=替代>
 
如果DEPTNO上有一個索引
 
高效:
 
    SELECT *
    FROM EMP
    WHERE DEPTNO >=4
    
   
低效:
 
    SELECT *
    FROM EMP
    WHERE DEPTNO >3
 
      
兩者的區別在於, 前者DBMS將直接跳到第一個DEPT等於4的記錄而後者將首先定位到DEPTNO=3的記錄並且向前掃描到第一個DEPT大於3的記錄.
36.      
UNION替換OR (適用於索引列)
通常情況下, UNION替換WHERE子句中的OR將會起到較好的效果. 對索引列使用OR將造成全表掃描. 注意, 以上規則只針對多個索引列有效. 如果有column沒有被索引, 查詢效率可能會因為你沒有選擇OR而降低
   
在下面的例子中, LOC_ID REGION上都建有索引.
高效:
    SELECT LOC_ID , LOC_DESC , REGION
    FROM LOCATION
    WHERE LOC_ID = 10
    UNION
    SELECT LOC_ID , LOC_DESC , REGION
    FROM LOCATION
    WHERE REGION = “MELBOURNE”
 
低效:
    SELECT LOC_ID , LOC_DESC , REGION
    FROM LOCATION
    WHERE LOC_ID = 10 OR REGION = “MELBOURNE”
 
如果你堅持要用OR, 那就需要返回記錄最少的索引列寫在最前面.
 
注意:
 
WHERE KEY1 = 10   (
返回最少記錄)
OR KEY2 = 20        (
返回最多記錄)
 
ORACLE 內部將以上轉換為
WHERE KEY1 = 10 AND
((NOT KEY1 = 10) AND KEY2 = 20)        
 
譯者按
 
下面的測試資料僅供參考: (a = 1003 返回一條記錄 , b = 1 返回1003條記錄)
SQL> select * from unionvsor /*1st test*/
   2   where a = 1003 or b = 1;
1003 rows selected.
Execution Plan
----------------------------------------------------------
    0      SELECT STATEMENT ptimizer=CHOOSE
    1    0   CONCATENATION
    2    1     TABLE ACCESS (BY INDEX ROWID) OF 'UNIONVSOR'
    3    2       INDEX (RANGE SCAN) OF 'UB' (NON-UNIQUE)
    4    1     TABLE ACCESS (BY INDEX ROWID) OF 'UNIONVSOR'
    5    4       INDEX (RANGE SCAN) OF 'UA' (NON-UNIQUE)
Statistics
----------------------------------------------------------
           0  recursive calls
           0  db block gets
         144  consistent gets
           0  physical reads
           0  redo size
       63749  bytes sent via SQL*Net to client
        7751  bytes received via SQL*Net from client
          68  SQL*Net roundtrips to/from client
           0  sorts (memory)
           0  sorts (disk)
        1003  rows processed
SQL> select * from unionvsor /*2nd test*/
   2  where b  = 1 or a = 1003 ; 
1003 rows selected.
Execution Plan
----------------------------------------------------------
    0      SELECT STATEMENT ptimizer=CHOOSE
    1    0   CONCATENATION
    2    1     TABLE ACCESS (BY INDEX ROWID) OF 'UNIONVSOR'
    3    2       INDEX (RANGE SCAN) OF 'UA' (NON-UNIQUE)
    4    1     TABLE ACCESS (BY INDEX ROWID) OF 'UNIONVSOR'
    5    4       INDEX (RANGE SCAN) OF 'UB' (NON-UNIQUE)
Statistics
----------------------------------------------------------
           0  recursive calls
           0  db block gets
         143  consistent gets
           0  physical reads
           0  redo size
       63749  bytes sent via SQL*Net to client
        7751  bytes received via SQL*Net from client
          68  SQL*Net roundtrips to/from client
           0  sorts (memory)
           0  sorts (disk)
        1003  rows processed
 
SQL> select * from unionvsor /*3rd test*/
   2  where a = 1003
   3  union 
   4   select * from unionvsor
   5   where b = 1;
1003 rows selected.
Execution Plan
----------------------------------------------------------
    0      SELECT STATEMENT ptimizer=CHOOSE
    1    0   SORT (UNIQUE)
    2    1     UNION-ALL
    3    2       TABLE ACCESS (BY INDEX ROWID) OF 'UNIONVSOR'
    4    3         INDEX (RANGE SCAN) OF 'UA' (NON-UNIQUE)
    5    2       TABLE ACCESS (BY INDEX ROWID) OF 'UNIONVSOR'
    6    5         INDEX (RANGE SCAN) OF 'UB' (NON-UNIQUE)
Statistics
----------------------------------------------------------
           0  recursive calls
           0  db block gets
          10  consistent gets   
           0  physical reads
           0  redo size
       63735  bytes sent via SQL*Net to client
        7751  bytes received via SQL*Net from client
          68  SQL*Net roundtrips to/from client
           1  sorts (memory)
           0  sorts (disk)
        1003  rows processed
UNION的效果可以從consistent gets SQL*NET的資料交換量的減少看出
 
37.      
IN來替換OR
 
下面的查詢可以被更有效率的語句替換:
 
低效:
 
SELECT….
FROM LOCATION
WHERE LOC_ID = 10
OR     LOC_ID = 20
OR     LOC_ID = 30
 
高效
SELECT…
FROM LOCATION
WHERE LOC_IN IN (10,20,30);
     
譯者按:
這是一條簡單易記的規則,但是實際的執行效果還須檢驗,在ORACLE8i下,兩者的執行路徑似乎是相同的. 
 
38.      
避免在索引列上使用IS NULLIS NOT NULL
避免在索引中使用任何可以為空的列,ORACLE將無法使用該索引 .對於單列索引,如果列包含空值,索引中將不存在此記錄. 對於複合索引,如果每個列都為空,索引中同樣不存在此記錄. 如果至少有一個列不為空,則記錄存在於索引中.
舉例:
  
如果唯一性索引建立在表的A列和B列上, 並且表中存在一條記錄的A,B值為(123,null) , ORACLE將不接受下一條具有相同A,B值(123,null)的記錄(插入). 然而如果
所有的索引列都為空,ORACLE將認為整個鍵值為空而空不等於空. 因此你可以插入1000
條具有相同鍵值的記錄,當然它們都是空!
 
      
因為空值不存在於索引列中,所以WHERE子句中對索引列進行空值比較將使ORACLE停用該索引.
舉例:
 
低效: (索引失效)
SELECT …
FROM DEPARTMENT
WHERE DEPT_CODE IS NOT NULL;
 
高效: (索引有效)
SELECT …
FROM DEPARTMENT
WHERE DEPT_CODE >=0;
39.      
總是使用索引的第一個列
如果索引是建立在多個列上, 只有在它的第一個列(leading column)where子句引用時,優化器才會選擇使用該索引
 
譯者按:
這也是一條簡單而重要的規則. 見以下例項.
 
SQL> create table multiindexusage ( inda number , indb number , descr varchar2(10));
Table created.
SQL> create index multindex on multiindexusage(inda,indb);
Index created.
SQL> set autotrace traceonly
 
SQL>  select * from  multiindexusage where inda = 1;
Execution Plan
----------------------------------------------------------
    0      SELECT STATEMENT ptimizer=CHOOSE
    1    0   TABLE ACCESS (BY INDEX ROWID) OF 'MULTIINDEXUSAGE'
    2    1     INDEX (RANGE SCAN) OF 'MULTINDEX' (NON-UNIQUE)
 
SQL> select * from  multiindexusage where indb = 1;
Execution Plan
----------------------------------------------------------
    0      SELECT STATEMENT ptimizer=CHOOSE
    1    0   TABLE ACCESS (FULL) OF 'MULTIINDEXUSAGE'
    
很明顯, 當僅引用索引的第二個列時,優化器使用了全表掃描而忽略了索引
 
40.      
ORACLE內部操作
當執行查詢時,ORACLE採用了內部的操作. 下表顯示了幾種重要的內部操作.
ORACLE Clause  內部操作
ORDER BY  SORT ORDER BY
UNION  UNION-ALL
MINUS  MINUS
INTERSECT  INTERSECT
DISTINCT,MINUS,INTERSECT,UNION  SORT UNIQUE
MIN,MAX,COUNT  SORT AGGREGATE
GROUP BY  SORT GROUP BY
ROWNUM  COUNT or COUNT STOPKEY
Queries involving Joins  SORT JOIN,MERGE JOIN,NESTED LOOPS
CONNECT BY  CONNECT BY
 
41.      
UNION-ALL 替換UNION ( 如果有可能的話)
 
SQL語句需要UNION兩個查詢結果集合時,這兩個結果集合會以UNION-ALL的方式被合併, 然後在輸出最終結果前進行排序.
如果用UNION ALL替代UNION, 這樣排序就不是必要了. 效率就會因此得到提高.
 
舉例:
   
低效:
    SELECT ACCT_NUM, BALANCE_AMT
         FROM DEBIT_TRANSACTIONS
         WHERE TRAN_DATE = ’31-DEC-95’
         UNION
         SELECT ACCT_NUM, BALANCE_AMT
         FROM DEBIT_TRANSACTIONS
         WHERE TRAN_DATE = ’31-DEC-95’
高效:
         SELECT ACCT_NUM, BALANCE_AMT
         FROM DEBIT_TRANSACTIONS
         WHERE TRAN_DATE = ’31-DEC-95’
         UNION ALL
         SELECT ACCT_NUM, BALANCE_AMT
         FROM DEBIT_TRANSACTIONS
         WHERE TRAN_DATE = ’31-DEC-95’
 
譯者按:
需要注意的是,UNION ALL 將重複輸出兩個結果集合中相同記錄. 因此各位還是
要從業務需求分析使用UNION ALL的可行性.
UNION
將對結果集合排序,這個操作會使用到SORT_AREA_SIZE這塊記憶體. 對於這
塊記憶體的優化也是相當重要的. 下面的SQL可以用來查詢排序的消耗量
 
Select substr(name,1,25)  "Sort Area Name",
      substr(value,1,15)   "Value"
from v$sysstat
where name like 'sort%'
     
42.      
使用提示(Hints)
對於表的訪問,可以使用兩種Hints.
FULL
ROWID
 
FULL hint
告訴ORACLE使用全表掃描的方式訪問指定表.
例如:
    SELECT /*+ FULL(EMP) */ *
    FROM EMP
    WHERE EMPNO = 7893;
 
    ROWID hint
告訴ORACLE使用TABLE ACCESS BY ROWID的操作訪問表.
 
   
通常, 你需要採用TABLE ACCESS BY ROWID的方式特別是當訪問大表的時候, 使用這種方式, 你需要知道ROIWD的值或者使用索引.
   
如果一個大表沒有被設定為快取(CACHED)表而你希望它的資料在查詢結束是仍然停留
SGA,你就可以使用CACHE hint 來告訴優化器把資料保留在SGA. 通常CACHE hint FULL hint 一起使用.
例如:
SELECT /*+ FULL(WORKER) CACHE(WORKER)*/ *
FROM WORK;
 
   
索引hint 告訴ORACLE使用基於索引的掃描方式. 你不必說明具體的索引名稱
例如:
    SELECT /*+ INDEX(LODGING) */ LODGING
    FROM LODGING
    WHERE MANAGER = ‘BILL GATES’;
    
   
在不使用hint的情況下, 以上的查詢應該也會使用索引,然而,如果該索引的重複值過多而你的優化器是CBO, 優化器就可能忽略索引. 在這種情況下, 你可以用INDEX hint強制ORACLE使用該索引.
 
   
ORACLE hints 還包括ALL_ROWS, FIRST_ROWS, RULE,USE_NL, USE_MERGE, USE_HASH 等等.
    
譯者按:
   
使用hint , 表示我們對ORACLE優化器預設的執行路徑不滿意,需要手工修改.
這是一個很有技巧性的工作. 我建議只針對特定的,少數的SQL進行hint優化.
ORACLE優化器還是要有信心(特別是CBO)
43.      
WHERE替代ORDER BY
ORDER BY
子句只在兩種嚴格的條件下使用索引.
 
ORDER BY
中所有的列必須包含在相同的索引中並保持在索引中的排列順序.
ORDER BY
中所有的列必須定義為非空.
 
WHERE
子句使用的索引和ORDER BY子句中所使用的索引不能並列.
 
例如:
      
DEPT包含以下列:
 
         DEPT_CODE    PK    NOT NULL
         DEPT_DESC           NOT NULL
         DEPT_TYPE           NULL
     
       
非唯一性的索引(DEPT_TYPE)
 
     
低效: (索引不被使用)
             SELECT DEPT_CODE
             FROM DEPT
             ORDER BY DEPT_TYPE
 
        EXPLAIN PLAN:
             SORT ORDER BY 
                   TABLE ACCESS FULL
     
高效: (使用索引)
            SELECT DEPT_CODE
            FROM DEPT
         WHERE DEPT_TYPE > 0 
     EXPLAIN PLAN:
       TABLE ACCESS BY ROWID ON EMP
              INDEX RANGE SCAN ON DEPT_IDX
譯者按:
       ORDER BY
也能使用索引! 這的確是個容易被忽視的知識點. 我們來驗證一下:
SQL>  select * from emp order by empno;
Execution Plan
----------------------------------------------------------
    0      SELECT STATEMENT ptimizer=CHOOSE
    1    0   TABLE ACCESS (BY INDEX ROWID) OF 'EMP'
    2    1     INDEX (FULL SCAN) OF 'EMPNO' (UNIQUE)
44.      
避免改變索引列的型別.
當比較不同資料型別的資料時, ORACLE自動對列進行簡單的型別轉換.
假設 EMPNO是一個數值型別的索引列.
SELECT …
FROM EMP
WHERE EMPNO = ‘123’
實際上,經過ORACLE型別轉換, 語句轉化為:
SELECT …
FROM EMP
WHERE EMPNO = TO_NUMBER(‘123’)
幸運的是,型別轉換沒有發生在索引列上,索引的用途沒有被改變.
現在,假設EMP_TYPE是一個字元型別的索引列.
SELECT …
FROM EMP
WHERE EMP_TYPE = 123
這個語句被ORACLE轉換為:
SELECT …
FROM EMP
WHERE TO_NUMBER(EMP_TYPE)=123
因為內部發生的型別轉換, 這個索引將不會被用到
譯者按:
為了避免ORACLE對你的SQL進行隱式的型別轉換, 最好把型別轉換用顯式表現出來. 注意當字元和數值比較時, ORACLE會優先轉換數值型別到字元型別.
45.      
需要當心的WHERE子句
某些SELECT 語句中的WHERE子句不使用索引. 這裡有一些例子.
在下面的例子裡, ‘!=’ 將不使用索引. 記住, 索引只能告訴你什麼存在於表中, 而不能告訴你什麼不存在於表中
不使用索引:
SELECT ACCOUNT_NAME
FROM TRANSACTION
WHERE AMOUNT !=0;
使用索引:
SELECT ACCOUNT_NAME
FROM TRANSACTION
WHERE AMOUNT >0;
下面的例子中, ‘||’是字元連線函式. 就象其他函式那樣, 停用了索引.
不使用索引:
SELECT ACCOUNT_NAME,AMOUNT
FROM TRANSACTION
WHERE ACCOUNT_NAME||ACCOUNT_TYPE=’AMEXA’;
使用索引:
SELECT ACCOUNT_NAME,AMOUNT
FROM TRANSACTION
WHERE ACCOUNT_NAME = ‘AMEX’
AND  ACCOUNT_TYPE=’ A’;
下面的例子中, ‘+’是數學函式. 就象其他數學函式那樣, 停用了索引.
不使用索引:
SELECT ACCOUNT_NAME, AMOUNT
FROM TRANSACTION
WHERE AMOUNT + 3000 >5000;
使用索引:
SELECT ACCOUNT_NAME, AMOUNT
FROM TRANSACTION
WHERE AMOUNT > 2000 ;
下面的例子中,相同的索引列不能互相比較,這將會啟用全表掃描.
不使用索引:
SELECT ACCOUNT_NAME, AMOUNT
FROM TRANSACTION
WHERE ACCOUNT_NAME = NVL(:ACC_NAME,ACCOUNT_NAME);
使用索引:
SELECT ACCOUNT_NAME, AMOUNT
FROM TRANSACTION
WHERE ACCOUNT_NAME LIKE NVL(:ACC_NAME,’%’);
譯者按:
如果一定要對使用函式的列啟用索引, ORACLE新的功能: 基於函式的索引(Function-Based Index) 也許是一個較好的方案.
  CREATE INDEX EMP_I ON EMP (UPPER(ename)); /*
建立基於函式的索引*/
  SELECT * FROM emp WHERE UPPER(ename) = ‘BLACKSNAIL’; /*
將使用索引*/
46.      
連線多個掃描
如果你對一個列和一組有限的值進行比較, 優化器可能執行多次掃描並對結果進行合併連線.
舉例:
     SELECT * 
     FROM LODGING
     WHERE MANAGER IN (‘BILL GATES’,’KEN MULLER’);
    
優化器可能將它轉換成以下形式
     SELECT * 
     FROM LODGING
     WHERE MANAGER = ‘BILL GATES’
     OR MANAGER = ’KEN MULLER’;
    
當選擇執行路徑時, 優化 可能對每個條件採用LODGING$MANAGER上的索引範圍掃描. 返回的ROWID用來訪問LODGING表的記錄 (通過TABLE ACCESS BY ROWID 的方式). 最後兩組記錄以連線(CONCATENATION)的形式被組合成一個單一的集合.
Explain Plan :
SELECT STATEMENT ptimizer=CHOOSE
    CONCATENATION
       TABLE ACCESS (BY INDEX ROWID) OF LODGING
          INDEX (RANGE SCAN ) OF LODGING$MANAGER (NON-UNIQUE)
      TABLE ACCESS (BY INDEX ROWID) OF LODGING
          INDEX (RANGE SCAN ) OF LODGING$MANAGER (NON-UNIQUE)
譯者按:
本節和第37節似乎有矛盾之處
47.       CBO
下使用更具選擇性的索引
基於成本的優化(CBO, Cost-Based Optimizer)對索引的選擇性進行判斷來決定索引的使用是否能提高效率.
如果索引有很高的選擇性, 那就是說對於每個不重複的索引鍵值,只對應數量很少的記錄.
比如, 表中共有100條記錄而其中有80個不重複的索引鍵值. 這個索引的選擇性就是80/100 = 0.8 . 選擇性越高, 通過索引鍵值檢索出的記錄就越少
如果索引的選擇性很低, 檢索資料就需要大量的索引範圍查詢操作和ROWID 訪問表的
操作. 也許會比全表掃描的效率更低.
譯者按:
下列經驗請參閱:
a.      
如果檢索資料量超過30%的表中記錄數.使用索引將沒有顯著的效率提高
b.      
在特定情況下, 使用索引也許會比全表掃描慢, 但這是同一個數量級上的
區別. 而通常情況下,使用索引比全表掃描要塊幾倍乃至幾千倍!
48.      
避免使用耗費資源的操作
帶有DISTINCT,UNION,MINUS,INTERSECT,ORDER BYSQL語句會啟動SQL引擎
執行耗費資源的排序(SORT)功能. DISTINCT需要一次排序操作, 而其他的至少需要執行兩次排序.
,一個UNION查詢,其中每個查詢都帶有GROUP BY子句, GROUP BY會觸發嵌入排序(NESTED SORT) ; 這樣, 每個查詢需要執行一次排序, 然後在執行UNION, 又一個唯一排序(SORT UNIQUE)操作被執行而且它只能在前面的嵌入排序結束後才能開始執行. 嵌入的排序的深度會大大影響查詢的效率.
通常, 帶有UNION, MINUS , INTERSECTSQL語句都可以用其他方式重寫.
譯者按:
     
如果你的資料庫的SORT_AREA_SIZE調配得好, 使用UNION , MINUS, INTERSECT也是可以考慮的, 畢竟它們的可讀性很強
49.      
優化GROUP BY
提高GROUP BY 語句的效率, 可以通過將不需要的記錄在GROUP BY 之前過濾掉.下面兩個查詢返回相同結果但第二個明顯就快了許多.
低效:
    SELECT JOB , AVG(SAL)
    FROM EMP
    GROUP JOB 
    HAVING JOB = ‘PRESIDENT’
    OR JOB = ‘MANAGER’
 
高效:
    SELECT JOB , AVG(SAL)
    FROM EMP
    WHERE JOB = ‘PRESIDENT’
    OR JOB = ‘MANAGER’
    GROUP JOB 
    
譯者按:
     
本節和14節相同. 可略過
50.      
使用日期 
當使用日期是,需要注意如果有超過5位小數加到日期上, 這個日期會進到下一天!
例如:
1.
SELECT TO_DATE(‘01-JAN-93’+.99999)
FROM DUAL;
Returns:
    ’01-JAN-93 23:59:59’
2.
SELECT TO_DATE(‘01-JAN-93’+.999999)
FROM DUAL;
 
Returns:
    ’02-JAN-93 00:00:00’
譯者按:
    
雖然本節和SQL效能優化沒有關係, 但是作者的功力可見一斑
51.  
使用顯式的遊標(CURSORs)
使用隱式的遊標,將會執行兩次操作. 第一次檢索記錄, 第二次檢查TOO MANY ROWS 這個exception . 而顯式遊標不執行第二次操作
52.  
優化EXPORTIMPORT
使用較大的BUFFER(比如10MB , 10,240,000)可以提高EXPORTIMPORT的速度.
ORACLE將盡可能地獲取你所指定的記憶體大小,即使在記憶體不滿足,也不會報錯.這個值至少要和表中最大的列相當,否則列值會被截斷
譯者按:
可以肯定的是, 增加BUFFER會大大提高EXPORT , IMPORT的效率. (曾經碰到過一個CASE, 增加BUFFER,IMPORT/EXPORT快了10!) 
作者可能犯了一個錯誤: “這個值至少要和表中最大的列相當,否則列值會被截斷. “
其中最大的列也許是指最大的記錄大小.
關於EXPORT/IMPORT優化,CSDN論壇中有一些總結性的貼子,比如關於BUFFER引數, COMMIT引數等等, 詳情請查.
 
53.  
分離表和索引
總是將你的表和索引建立在不同的表空間內(TABLESPACES). 決不要將不屬於ORACLE內部系統的物件存放到SYSTEM表空間裡. 同時,確保資料表空間和索引表空間置於不同的硬碟上.
 
譯者按:
同時,確保資料表空間和索引表空間置與不同的硬碟上.”可能改為如下更為準確同時,確保資料表空間和索引表空間置與不同的硬碟控制卡控制的硬碟上.”
  (
全文完)
 

 

                    Oracle  sql 效能優化調整
1.
選用適合的ORACLE優化
    
ORACLE優化器共有3:
    a.  RULE (
基於規則)   b. COST (基於成本)  c. CHOOSE (選擇性)
    
設定預設的優化,可以通過對init.ora檔案中OPTIMIZER_MODE引數的各種宣告,RULE,COST,CHOOSE,ALL_ROWS,FIRST_ROWS . 你當然也在SQL句級或是會話(session)級對其進行覆蓋.
   
為了使用基於成本的優化(CBO, Cost-Based Optimizer) , 你必須經常執行analyze 命令,以增加資料庫中的物件統計資訊(object statistics)的準確性.
   
如果資料庫的優化器模式設定為選擇性(CHOOSE),那麼實際的優化器模式將和是否執行過analyze命令有關. 如果table已經被analyze, 優化器模式將自動成為CBO , 反之,資料庫將採用RULE形式的優化.
   
在預設情況下,ORACLE採用CHOOSE優化, 為了避免那些不必要的全表掃描(full table scan) , 你必須儘量避免使用CHOOSE優化,而直接採用基於規則或者基於成本的優化.
   2.      
訪問Table的方式
  
ORACLE 採用兩種訪問表中記錄的方式:
a.      
全表掃描 
            
全表掃描就是順序地訪問表中每條記錄. ORACLE採用一次讀入多個資料塊(database block)的方式優化全表掃描.
     b.      
通過ROWID訪問表
       
你可以採用基於ROWID的訪問方式情況,提高訪問表的效率, , ROWID包含了表中記錄的物理位置資訊..ORACLE採用索引(INDEX)實現了資料和存放資料的物理位置(ROWID)之間的聯絡. 通常索引提供了快速訪問ROWID的方法,因此那些基於索引列的查詢就可以得到效能上的提高.
  3.      
共享SQL語句
為了不重複解析相同的SQL語句,在第一次解析之後, ORACLESQL語句存放在記憶體中.這塊位於系統全域性區域SGA(system global area)的共享池(shared buffer pool)中的記憶體可以被所有的資料庫使用者共享. 因此,當你執行一個SQL語句(有時被稱為一個遊標),如果它
和之前的執行過的語句完全相同, ORACLE就能很快獲得已經被解析的語句以及最好的
執行路徑. ORACLE的這個功能大大地提高了SQL的執行效能並節省了記憶體的使用.
     
可惜的是ORACLE只對簡單的表提供高速緩衝(cache buffering) ,這個功能並不適用於多表連線查詢.
資料庫管理員必須在init.ora中為這個區域設定合適的引數,當這個記憶體區域越大,就可以保留更多的語句,當然被共享的可能性也就越大了.
當你向ORACLE 提交一個SQL語句,ORACLE會首先在這塊記憶體中查詢相同的語句.
 
這裡需要註明的是,ORACLE對兩者採取的是一種嚴格匹配,要達成共享,SQL語句必須
完全相同(包括空格,換行等).
     
共享的語句必須滿足三個條件:
  A.     
字元級的比較:
當前被執行的語句和共享池中的語句必須完全相同.
      
例如:
           SELECT * FROM EMP;
      
和下列每一個都不同
           SELECT * from EMP;
           Select * From Emp;
           SELECT      *     FROM EMP;
B.     
兩個語句所指的物件必須完全相同:
例如:
   
使用者  物件名    如何訪問
Jack  sal_limit    private synonym
     Work_city  public synonym
     Plant_detail  public synonym
Jill    sal_limit    private synonym
     Work_city  public synonym
     Plant_detail  table owner
     
考慮一下下列SQL語句能否在這兩個使用者之間共享.
  SQL 
能否共享  原因
select max(sal_cap) from sal_limit; 
不能  每個使用者都有一個private synonym - sal_limit , 它們是不同的物件
select count(*0 from work_city where sdesc like 'NEW%'; 
  兩個使用者訪問相同的物件public synonym - work_city 
select a.sdesc,b.location from work_city a , plant_detail b where a.city_id = b.city_id 
不能  使用者jack 通過private synonym訪問plant_detail jill 是表的所有者,物件不同.
  C.     
兩個SQL語句中必須使用相同的名字的繫結變數(bind variables)
例如:第一組的兩個SQL語句是相同的(可以共享),而第二組中的兩個語句是不同的(即使在執行時,賦於不同的繫結變數相同的值)
a.
select pin , name from people where pin = :blk1.pin;
select pin , name from people where pin = :blk1.pin;
b.
select pin , name from people where pin = :blk1.ot_ind;
select pin , name from people where pin = :blk1.ov_ind;
4.
選擇最有效率的表名順序(只在基於規則的優化器中有效)
ORACLE的解析器按照從右到左的順序處理FROM子句中的表名,因此FROM子句中寫在最後的表(基礎表 driving table)將被最先處理. FROM子句中包含多個表的情況下,你必須選擇記錄條數最少的表作為基礎表.ORACLE處理多個表時, 會運用排序及合併的方式連線它們.首先,掃描第一個表(FROM子句中最後的那個表)並對記錄進行派序,然後掃描第二個表(FROM子句中最後第二個表),最後將所有從第二個表中檢索出的記錄與第一個表中合適記錄進行合併.
例如:      TAB1 16,384 條記錄
         
TAB2 1      條記錄
     
選擇TAB2作為基礎表 (最好的方法)
      select count(*) from tab1,tab2  
執行時間0.96
     
選擇TAB2作為基礎表 (不佳的方法)
      select count(*) from tab2,tab1  
執行時間26.09
如果有3個以上的表連線查詢, 那就需要選擇交叉表(intersection table)作為基礎表, 交叉表是指那個被其他表所引用的表.
例如:   EMP表描述了LOCATION表和CATEGORY表的交集.
SELECT * 
FROM LOCATION L , 
       CATEGORY C,
       EMP E 
WHERE E.EMP_NO BETWEEN 1000 AND 2000
AND E.CAT_NO = C.CAT_NO
AND E.LOCN = L.LOCN
 
將比下列SQL更有效率
SELECT * 
FROM EMP E ,
LOCATION L , 
       CATEGORY C
WHERE  E.CAT_NO = C.CAT_NO
AND E.LOCN = L.LOCN
AND E.EMP_NO BETWEEN 1000 AND 2000
5.       WHERE
子句中的連線順序.
   
ORACLE採用自下而上的順序解析WHERE子句,根據這個原理,表之間的連線必須寫在其他WHERE條件之前, 那些可以過濾掉最大數量記錄的條件必須寫在WHERE子句的末尾.
 
例如:
  (
低效,執行時間156.3)
SELECT … 
FROM EMP E
WHERE  SAL > 50000
AND    JOB = ‘MANAGER’
AND    25 < (SELECT COUNT(*) FROM EMP
              WHERE MGR=E.EMPNO);
  (
高效,執行時間10.6)
SELECT … 
FROM EMP E
WHERE 25 < (SELECT COUNT(*) FROM EMP
              WHERE MGR=E.EMPNO)
AND    SAL > 50000
AND    JOB = ‘MANAGER’;
6.     SELECT
子句中避免使用 ‘ * ‘
當你想在SELECT子句中列出所有的COLUMN,使用動態SQL列引用 ‘*’ 是一個方便的方法.不幸的是,這是一個非常低效的方法. 實際上,ORACLE在解析的過程中, 會將’*’ 依次轉換成所有的列名, 這個工作是通過查詢資料字典完成的, 這意味著將耗費更多的時間
7.    
減少訪問資料庫的次數
當執行每條SQL語句時, ORACLE在內部執行了許多工作: 解析SQL語句, 估算索引的利用率, 繫結變數 , 讀資料塊等等. 由此可見, 減少訪問資料庫的次數 , 就能實際上減少ORACLE的工作量.
 
例如,
    
以下有三種方法可以檢索出僱員號等於03420291的職員.
 
方法1 (最低效)
     SELECT EMP_NAME , SALARY , GRADE
     FROM EMP 
     WHERE EMP_NO = 342;
      SELECT EMP_NAME , SALARY , GRADE
     FROM EMP 
     WHERE EMP_NO = 291;
方法2 (次低效)
        DECLARE 
         CURSOR C1 (E_NO NUMBER) IS 
         SELECT EMP_NAME,SALARY,GRADE
         FROM EMP 
         WHERE EMP_NO = E_NO;
     BEGIN 
         OPEN C1(342);
         FETCH C1 INTO …,..,.. ;
                 OPEN C1(291);
        FETCH C1 INTO …,..,.. ;
          CLOSE C1;
       END;
方法3 (高效)
     SELECT A.EMP_NAME , A.SALARY , A.GRADE,
             B.EMP_NAME , B.SALARY , B.GRADE
     FROM EMP A,EMP B
     WHERE A.EMP_NO = 342
     AND   B.EMP_NO = 291;
 
注意:
SQL*Plus , SQL*FormsPro*C中重新設定ARRAYSIZE引數, 可以增加每次資料庫訪問的檢索資料量 ,建議值為200.
8.      
使用DECODE函式來減少處理時間
使用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子句中.
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只在刪除全表適用,TRUNCATEDDL不是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.      
使用表的別名(Alias)
當在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工具找出低效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工具來解決問題始終是一個最好的方法)
  23.      
使用TKPROF 工具來查詢SQL效能狀態
  SQL trace
工具收集正在執行的SQL的效能狀態資料並記錄到一個跟蹤檔案中. 這個跟蹤檔案提供了許多有用的資訊,例如解析次數.執行次數,CPU使用時間等.這些資料將可以用來優化你的系統.
設定SQL TRACE在會話級別: 有效
    ALTER SESSION SET SQL_TRACE TRUE
設定SQL TRACE 在整個資料庫有效仿, 你必須將SQL_TRACE引數在init.ora中設為TRUE, USER_DUMP_DEST引數說明了生成跟蹤檔案的目錄
   (
譯者按: 這一節中,作者並沒有提到TKPROF的用法, SQL TRACE的用法也不夠準確, 設定SQL TRACE首先要在init.ora中設定TIMED_STATISTICS, 這樣才能得到那些重要的時間狀態. 生成的trace檔案是不可讀的,所以要用TKPROF工具對其進行轉換,TKPROF有許多執行引數. 大家可以參考ORACLE手冊來了解具體的配置. )
24.      
EXPLAIN PLAN 分析SQL語句
  EXPLAIN PLAN
是一個很好的分析SQL語句的工具,它甚至可以在不執行SQL的情況下分析語句. 通過分析,我們就可以知道ORACLE是怎麼樣連線表,使用什麼方式掃描表(索引掃描或全表掃描)以及使用到的索引名稱.
你需要按照從裡到外,從上到下的次序解讀分析的結果. EXPLAIN PLAN分析的結果是用縮排的格式排列的, 最內部的操作將被最先解讀, 如果兩個操作處於同一層中,帶有最小操作號的將被首先執行.
NESTED LOOP
是少數不按照上述規則處理的操作, 正確的執行路徑是檢查對NESTED LOOP提供資料的操作,其中操作號最小的將被最先處理.
譯者按
 
通過實踐, 感到還是用SQLPLUS中的SET TRACE 功能比較方便.
舉例:
  SQL> list
   1  SELECT *
   2  FROM dept, emp
   3* WHERE emp.deptno = dept.deptno
SQL> set autotrace traceonly /*traceonly
可以不顯示執行結果*/
SQL> /
14 rows selected.
Execution Plan
----------------------------------------------------------
    0      SELECT STATEMENT ptimizer=CHOOSE
    1    0   NESTED LOOPS
    2    1     TABLE ACCESS (FULL) OF 'EMP' 
    3    1     TABLE ACCESS (BY INDEX ROWID) OF 'DEPT'
    4    3       INDEX (UNIQUE SCAN) OF 'PK_DEPT' (UNIQUE)
  
Statistics
----------------------------------------------------------
           0  recursive calls
           2  db block gets
          30  consistent gets
           0  physical reads
           0  redo size
        2598  bytes sent via SQL*Net to client
         503  bytes received via SQL*Net from client
           2  SQL*Net roundtrips to/from client
           0  sorts (memory)
           0  sorts (disk)
          14  rows processed
通過以上分析,可以得出實際的執行步驟是:
1.       TABLE ACCESS (FULL) OF 'EMP' 
2.       INDEX (UNIQUE SCAN) OF 'PK_DEPT' (UNIQUE)
3.       TABLE ACCESS (BY INDEX ROWID) OF 'DEPT'
4.       NESTED LOOPS (JOINING 1 AND 3)
: 目前許多第三方的工具如TOADORACLE本身提供的工具如OMSSQL Analyze都提供了極其方便的EXPLAIN PLAN工具.也許喜歡圖形化介面的朋友們可以選用它們.
25.      
用索引提高效率
 
索引是表的一個概念部分,用來提高檢索資料的效率. 實際上,ORACLE使用了一個複雜的自平衡B-tree結構. 通常,通過索引查詢資料比全表掃描要快. ORACLE找出執行查詢和Update語句的最佳路徑時, ORACLE優化器將使用索引. 同樣在聯結多個表時使用索引也可以提高效率. 另一個使用索引的好處是,它提供了主鍵(primary key)的唯一性驗證.
除了那些LONGLONG RAW資料型別, 你可以索引幾乎所有的列. 通常, 在大型表中使用索引特別有效. 當然,你也會發現, 在掃描小表時,使用索引同樣能提高效率.
雖然使用索引能得到查詢效率的提高,但是我們也必須注意到它的代價. 索引需要空間來
,也需要定期維護, 每當有記錄在表中增減或索引列被修改時, 索引本身也會被修改. 這意味著每條記錄的INSERT , DELETE , UPDATE將為此多付出4 , 5 次的磁碟I/O . 因為索引需要額外的儲存空間和處理,那些不必要的索引反而會使查詢反應時間變慢.
 
譯者按
定期的重構索引是有必要的
ALTER INDEX REBUILD
 
26.      
索引的操作
 
ORACLE對索引有兩種訪問模式.
 
索引唯一掃描 ( INDEX UNIQUE SCAN)
 
大多數情況下, 優化器通過WHERE子句訪問INDEX.
 
例如:
LODGING有兩個索引 : 建立在LODGING列上的唯一性索引LODGING_PK和建立在MANAGER列上的非唯一性索引LODGING$MANAGER. 
 
SELECT * 
FROM LODGING
WHERE LODGING = ‘ROSE HILL’;
 
   
在內部 , 上述SQL將被分成兩步執行, 首先 , LODGING_PK 索引將通過索引唯一掃描的方式被訪問 , 獲得相對應的ROWID, 通過ROWID訪問表的方式 執行下一步檢索.
   
如果被檢索返回的列包括在INDEX列中,ORACLE將不執行第二步的處理(通過ROWID訪問表). 因為檢索資料儲存在索引中, 單單訪問索引就可以完全滿足查詢結果
   
下面SQL只需要INDEX UNIQUE SCAN 操作.
         
         SELECT LODGING
         FROM  LODGING
WHERE LODGING = ‘ROSE HILL’;
 
  
索引範圍查詢(INDEX RANGE SCAN)
      
適用於兩種情況:
1.      
基於一個範圍的檢索
2.      
基於非唯一性索引的檢索
 
 
1:
 
       SELECT LODGING
       FROM  LODGING
WHERE LODGING LIKE ‘M%’;
 
WHERE
子句條件包括一系列值, ORACLE將通過索引範圍查詢的方式查詢LODGING_PK . 由於索引範圍查詢將返回一組值, 它的效率就要比索引唯一掃描
低一些
 
2:
 
       SELECT LODGING
       FROM  LODGING
WHERE MANAGER = ‘BILL GATES’;
  
  
這個SQL的執行分兩步, LODGING$MANAGER的索引範圍查詢(得到所有符合條件記錄的ROWID) 和下一步同過ROWID訪問表得到LODGING列的值. 由於LODGING$MANAGER是一個非唯一性的索引,資料庫不能對它執行索引唯一掃描
 
  
由於SQL返回LODGING,而它並不存在於LODGING$MANAGER索引中, 所以在索引範圍查詢後會執行一個通過ROWID訪問表的操作
 
   WHERE
子句中, 如果索引列所對應的值的第一個字元由萬用字元(WILDCARD)開始, 索引將不被採用.
 
  SELECT LODGING
       FROM  LODGING
WHERE MANAGER LIKE ‘
HANMAN’;
 
 
在這種情況下,ORACLE將使用全表掃描.
27.      
基礎表的選擇
 
基礎表(Driving Table)是指被最先訪問的表(通常以全表掃描的方式被訪問). 根據優化器的不同, SQL語句中基礎表的選擇是不一樣的.
如果你使用的是CBO (COST BASED OPTIMIZER),優化器會檢查SQL語句中的每個表的物理大小,索引的狀態,然後選用花費最低的執行路徑.
如果你用RBO (RULE BASED OPTIMIZER) , 並且所有的連線條件都有索引對應, 在這種情況下, 基礎表就是FROM 子句中列在最後的那個表.
舉例:
      SELECT A.NAME , B.MANAGER
      FROM
 WORKER A, 
              LODGING B
      WHERE
 A.LODGING = B.LODING;
由於LODGING表的LODING列上有一個索引, 而且WORKER表中沒有相比較的索引, WORKER表將被作為查詢中的基礎表.
 
28.      
多個平等的索引
SQL語句的執行路徑可以使用分佈在多個表上的多個索引時, ORACLE會同時使用多個索引並在執行時對它們的記錄進行合併, 檢索出僅對全部索引有效的記錄.
ORACLE選擇執行路徑時,唯一性索引的等級高於非唯一性索引. 然而這個規則只有
WHERE子句中索引列和常量比較才有效.如果索引列和其他表的索引類相比較. 這種子句在優化器中的等級是非常低的.
如果不同表中兩個想同等級的索引將被引用, FROM子句中表的順序將決定哪個會被率先使用. FROM子句中最後的表的索引將有最高的優先順序.
如果相同表中兩個想同等級的索引將被引用, WHERE子句中最先被引用的索引將有最高的優先順序.
舉例:
      DEPTNO
上有一個非唯一性索引,EMP_CAT也有一個非唯一性索引.
      SELECT ENAME,
      FROM EMP
      WHERE DEPT_NO = 20
      AND EMP_CAT = ‘A’;
這裡,DEPTNO索引將被最先檢索,然後同EMP_CAT索引檢索出的記錄進行合併. 執行路徑如下:
 
TABLE ACCESS BY ROWID ON EMP
     AND-EQUAL
         INDEX RANGE SCAN ON DEPT_IDX
         INDEX RANGE SCAN ON CAT_IDX
 
29.       
等式比較和範圍比較
     
WHERE子句中有索引列, ORACLE不能合併它們,ORACLE將用範圍比較.
 
     
舉例:
      DEPTNO
上有一個非唯一性索引,EMP_CAT也有一個非唯一性索引.
      SELECT ENAME
      FROM EMP
      WHERE DEPTNO > 20
      AND EMP_CAT = ‘A’;
     
     
這裡只有EMP_CAT索引被用到,然後所有的記錄將逐條與DEPTNO條件進行比較. 執行路徑如下:
      TABLE ACCESS BY ROWID ON EMP 
            INDEX RANGE SCAN ON CAT_IDX
 
30.      
不明確的索引等級
 
ORACLE無法判斷索引的等級高低差別,優化器將只使用一個索引,它就是在WHERE子句中被列在最前面的.
     
舉例:
      DEPTNO
上有一個非唯一性索引,EMP_CAT也有一個非唯一性索引.
      
      SELECT ENAME
      FROM EMP
      WHERE DEPTNO > 20
      AND EMP_CAT > ‘A’;
 
     
這裡, ORACLE只用到了DEPT_NO索引. 執行路徑如下:
      
      TABLE ACCESS BY ROWID ON EMP
           INDEX RANGE SCAN ON DEPT_IDX
 
譯者按:
我們來試一下以下這種情況:
SQL> select index_name, uniqueness from user_indexes where table_name = 'EMP';
 
INDEX_NAME                     UNIQUENES
------------------------------ ---------
EMPNO                          UNIQUE
EMPTYPE                        NONUNIQUE
 
SQL> select * from emp where empno >= 2 and emp_type = 'A' ;
 
no rows selected
 
Execution Plan
----------------------------------------------------------
    0      SELECT STATEMENT ptimizer=CHOOSE
    1    0   TABLE ACCESS (BY INDEX ROWID) OF 'EMP'
    2    1     INDEX (RANGE SCAN) OF 'EMPTYPE' (NON-UNIQUE)
  
雖然EMPNO是唯一性索引,但是由於它所做的是範圍比較, 等級要比非唯一性索引的等式比較低!
31.      
強制索引失效
 
    
如果兩個或以上索引具有相同的等級,你可以強制命令ORACLE優化器使用其中的一個(通過它,檢索出的記錄數量少) .
 
舉例:
    
SELECT ENAME
FROM EMP
WHERE EMPNO = 7935  
AND DEPTNO + 0 = 10    /*DEPTNO
上的索引將失效*/
AND EMP_TYPE || ‘’ = ‘A’  /*EMP_TYPE
上的索引將失效*/
 
這是一種相當直接的提高查詢效率的辦法. 但是你必須謹慎考慮這種策略,一般來說,只有在你希望單獨優化幾個SQL時才能採用它.
 
這裡有一個例子關於何時採用這種策略
 
假設在EMP表的EMP_TYPE列上有一個非唯一性的索引而EMP_CLASS上沒有索引
 
SELECT ENAME
FROM EMP
WHERE EMP_TYPE = ‘A’ 
AND EMP_CLASS = ‘X’;
 
優化器會注意到EMP_TYPE上的索引並使用它. 這是目前唯一的選擇. 如果,一段時間以後, 另一個非唯一性建立在EMP_CLASS,優化器必須對兩個索引進行選擇,在通常情況下,優化器將使用兩個索引並在他們的結果集合上執行排序及合併. 然而,如果其中一個索引(EMP_TYPE)接近於唯一性而另一個索引(EMP_CLASS)上有幾千個重複的值. 排序及合併就會成為一種不必要的負擔. 在這種情況下,你希望使優化器遮蔽掉EMP_CLASS索引.
用下面的方案就可以解決問題.
SELECT ENAME
FROM EMP
WHERE EMP_TYPE = ‘A’ 
AND EMP_CLASS||’’ = ‘X’;
 
32.      
避免在索引列上使用計算.
WHERE
子句中,如果索引列是函式的一部分.優化器將不使用索引而使用全表掃描.
 
舉例:
 
低效:
SELECT …
FROM DEPT
WHERE SAL * 12 > 25000;
 
高效:
SELECT …
FROM DEPT
WHERE SAL  > 25000/12;
 
譯者按:
這是一個非常實用的規則,請務必牢記
 
33.      
自動選擇索引
如果表中有兩個以上(包括兩個)索引,其中有一個唯一性索引,而其他是非唯一性.
在這種情況下,ORACLE將使用唯一性索引而完全忽略非唯一性索引.
 
舉例:
SELECT ENAME
FROM EMP
WHERE EMPNO = 2326  
AND DEPTNO  = 20 ;
 
這裡,只有EMPNO上的索引是唯一性的,所以EMPNO索引將用來檢索記錄.
TABLE ACCESS BY ROWID ON EMP
        INDEX UNIQUE SCAN ON EMP_NO_IDX
  
34.      
避免在索引列上使用NOT
通常, 我們要避免在索引列上使用NOT, NOT會產生在和在索引列上使用函式相同的
影響. ORACLE遇到”NOT,他就會停止使用索引轉而執行全表掃描.
   
舉例:
 
   
低效: (這裡,不使用索引)
 
    SELECT …
    FROM DEPT
    WHERE DEPT_CODE NOT = 0;
    
   
高效: (這裡,使用了索引)
 
   SELECT …
    FROM DEPT
    WHERE DEPT_CODE > 0;
 
   
需要注意的是,在某些時候, ORACLE優化器會自動將NOT轉化成相對應的關係操作符.
    NOT >  to  <=
    NOT >=  to  <
    NOT =
    NOT <=  to  >
  
 
譯者按:
     
在這個例子中,作者犯了一些錯誤. 例子中的低效率SQL是不能被執行的.
我做了一些測試:
      
SQL> select * from emp where NOT empno > 1;
no rows selected
Execution Plan
----------------------------------------------------------
    0      SELECT STATEMENT ptimizer=CHOOSE
    1    0   TABLE ACCESS (BY INDEX ROWID) OF 'EMP'
    2    1     INDEX (RANGE SCAN) OF 'EMPNO' (UNIQUE)     
 
SQL> select * from emp where empno <= 1;
no rows selected
Execution Plan
----------------------------------------------------------
    0      SELECT STATEMENT ptimizer=CHOOSE
    1    0   TABLE ACCESS (BY INDEX ROWID) OF 'EMP'
    2    1     INDEX (RANGE SCAN) OF 'EMPNO' (UNIQUE)
 
      
兩者的效率完全一樣,也許這符合作者關於在某些時候, ORACLE優化器會自動將NOT轉化成相對應的關係操作符的觀點.
       
35.      
>=替代>
 
如果DEPTNO上有一個索引
 
高效:
 
    SELECT *
    FROM EMP
    WHERE DEPTNO >=4
    
   
低效:
 
    SELECT *
    FROM EMP
    WHERE DEPTNO >3
 
      
兩者的區別在於, 前者DBMS將直接跳到第一個DEPT等於4的記錄而後者將首先定位到DEPTNO=3的記錄並且向前掃描到第一個DEPT大於3的記錄.
36.      
UNION替換OR (適用於索引列)
通常情況下, UNION替換WHERE子句中的OR將會起到較好的效果. 對索引列使用OR將造成全表掃描. 注意, 以上規則只針對多個索引列有效. 如果有column沒有被索引, 查詢效率可能會因為你沒有選擇OR而降低
   
在下面的例子中, LOC_ID REGION上都建有索引.
高效:
    SELECT LOC_ID , LOC_DESC , REGION
    FROM LOCATION
    WHERE LOC_ID = 10
    UNION
    SELECT LOC_ID , LOC_DESC , REGION
    FROM LOCATION
    WHERE REGION = “MELBOURNE”
 
低效:
    SELECT LOC_ID , LOC_DESC , REGION
    FROM LOCATION
    WHERE LOC_ID = 10 OR REGION = “MELBOURNE”
 
如果你堅持要用OR, 那就需要返回記錄最少的索引列寫在最前面.
 
注意:
 
WHERE KEY1 = 10   (
返回最少記錄)
OR KEY2 = 20        (
返回最多記錄)
 
ORACLE 內部將以上轉換為
WHERE KEY1 = 10 AND
((NOT KEY1 = 10) AND KEY2 = 20)        
 
譯者按
 
下面的測試資料僅供參考: (a = 1003 返回一條記錄 , b = 1 返回1003條記錄)
SQL> select * from unionvsor /*1st test*/
   2   where a = 1003 or b = 1;
1003 rows selected.
Execution Plan
----------------------------------------------------------
    0      SELECT STATEMENT ptimizer=CHOOSE
    1    0   CONCATENATION
    2    1     TABLE ACCESS (BY INDEX ROWID) OF 'UNIONVSOR'
    3    2       INDEX (RANGE SCAN) OF 'UB' (NON-UNIQUE)
    4    1     TABLE ACCESS (BY INDEX ROWID) OF 'UNIONVSOR'
    5    4       INDEX (RANGE SCAN) OF 'UA' (NON-UNIQUE)
Statistics
----------------------------------------------------------
           0  recursive calls
           0  db block gets
         144  consistent gets
           0  physical reads
           0  redo size
       63749  bytes sent via SQL*Net to client
        7751  bytes received via SQL*Net from client
          68  SQL*Net roundtrips to/from client
           0  sorts (memory)
           0  sorts (disk)
        1003  rows processed
SQL> select * from unionvsor /*2nd test*/
   2  where b  = 1 or a = 1003 ; 
1003 rows selected.
Execution Plan
----------------------------------------------------------
    0      SELECT STATEMENT ptimizer=CHOOSE
    1    0   CONCATENATION
    2    1     TABLE ACCESS (BY INDEX ROWID) OF 'UNIONVSOR'
    3    2       INDEX (RANGE SCAN) OF 'UA' (NON-UNIQUE)
    4    1     TABLE ACCESS (BY INDEX ROWID) OF 'UNIONVSOR'
    5    4       INDEX (RANGE SCAN) OF 'UB' (NON-UNIQUE)
Statistics
----------------------------------------------------------
           0  recursive calls
           0  db block gets
         143  consistent gets
           0  physical reads
           0  redo size
       63749  bytes sent via SQL*Net to client
        7751  bytes received via SQL*Net from client
          68  SQL*Net roundtrips to/from client
           0  sorts (memory)
           0  sorts (disk)
        1003  rows processed
 
SQL> select * from unionvsor /*3rd test*/
   2  where a = 1003
   3  union 
   4   select * from unionvsor
   5   where b = 1;
1003 rows selected.
Execution Plan
----------------------------------------------------------
    0      SELECT STATEMENT ptimizer=CHOOSE
    1    0   SORT (UNIQUE)
    2    1     UNION-ALL
    3    2       TABLE ACCESS (BY INDEX ROWID) OF 'UNIONVSOR'
    4    3         INDEX (RANGE SCAN) OF 'UA' (NON-UNIQUE)
    5    2       TABLE ACCESS (BY INDEX ROWID) OF 'UNIONVSOR'
    6    5         INDEX (RANGE SCAN) OF 'UB' (NON-UNIQUE)
Statistics
----------------------------------------------------------
           0  recursive calls
           0  db block gets
          10  consistent gets   
           0  physical reads
           0  redo size
       63735  bytes sent via SQL*Net to client
        7751  bytes received via SQL*Net from client
          68  SQL*Net roundtrips to/from client

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/25897606/viewspace-704149/,如需轉載,請註明出處,否則將追究法律責任。

相關文章