ORACLE高效sql

No1:選擇合適的優化器

No2:共享SQL

爲了不重複解析相同的SQL語句,在第一次解析之後, ORACLE將SQL語句存放在內存中.這塊位於系統全局區域SGA(system global area)的共享池(shared buffer pool)中的內存可以被所有的數據庫用戶共享. 因此,當你執行一個SQL語句(有時被稱爲一個遊標)時,如果它
和之前的執行過的語句完全相同, ORACLE就能很快獲得已經被解析的語句以及最好的
執行路徑. ORACLE的這個功能大大地提高了SQL的執行性能並節省了內存的使用.
可惜的是ORACLE只對簡單的表提供高速緩衝(cache buffering) ,這個功能並不適用於多表連接查詢.
數據庫管理員必須在init.ora中爲這個區域設置合適的參數,當這個內存區域越大,就可以保留更多的語句,當然被共享的可能性也就越大了.
當你向ORACLE 提交一個SQL語句,ORACLE會首先在這塊內存中查找相同的語句.
這裏需要註明的是,ORACLE對兩者採取的是一種嚴格匹配,要達成共享,SQL語句必須
完全相同(包括空格,換行等).
共享的語句必須滿足三個條件:
A. 字符級的比較:
B. 兩個語句所指的對象必須完全相同:
C. 兩個SQL語句中必須使用相同的名字的綁定變量(bind variables)

No3. 選擇最有效率的表名順序(只在基於規則的優化器中有效)

ORACLE 的解析器按照從右到左的順序處理FROM子句中的表名,因此FROM子句中寫在最後的表(基礎表 driving table)將被最先處理. 在FROM子句中包含多個表的情況下,你必須選擇記錄條數最少的表作爲基礎表.當ORACLE處理多個表時, 會運用排序及合併的方式連接它們.首先,掃描第一個表(FROM子句中最後的那個表)並對記錄進行排序,然後掃描第二個表(FROM子句中最後第二個表),最後將所有從第二個表中檢索出的記錄與第一個表中合適記錄進行合併.
如果有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

No4:WHERE子句中的連接順序

ORACLE採用自下而上的順序解析WHERE子句,根據這個原理,表之間的連接必須寫在其他WHERE條件之前, 那些可以過濾掉最大數量記錄的條件必須寫在WHERE子句的末尾.

No5:避免使用select *

當你想在SELECT子句中列出所有的COLUMN時,使用動態SQL列引用 ‘*’ 是一個方便的方法.不幸的是,這是一個非常低效的方法. 實際上,ORACLE在解析的過程中, 會將’*’ 依次轉換成所有的列名, 這個工作是通過查詢數據字典完成的, 這意味着將耗費更多的時間.

No6:減少訪問數據庫的次數

當執行每條SQL語句時, ORACLE在內部執行了許多工作: 解析SQL語句, 估算索引的利用率, 綁定變量 , 讀數據塊等等. 由此可見, 減少訪問數據庫的次數 , 就能實際上減少ORACLE的工作量.

No7:用TRUNCATE替代DELETE

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

No8:適當的使用commit

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

No9:減少對錶的查詢

在含有子查詢的SQL語句中,要特別注意減少對錶的查詢.
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;

No10:IN與EXIST

先說IN
他相當對inner table執行一個個帶有distinct的子查詢語句,然後得到的查詢結果集再與outer table進行連接,當然連接的方式和索引的使用仍然同於普通的兩表連接。
select * from T1 where x in (select y from T2);
可以轉換成如下
select * from
T1,(select distinct y from T2) T2
where T1.x=T2.y;
再說exists
實際上exists相當於對outer table進行全表掃描,用從中檢索到的每一行與inner table做循環匹配輸出相應的符合條件的結果,其主要開銷是對outer table的全表掃描(full scan),而連接方式是nested loop方式。
可以寫成
select * from T1 where exists (select NULL from T2 where T2.y=T1.x);
轉換成
for cursor1 in (select * from T1)
loop
if (exists (select NULL from T2 where T2.y=cursor1.x))
then
返回匹配的記錄;
end if;
end loop;
通過上面的解釋,現在很容易明白當T2數據量巨大且索引情況不好(大量重複值等),則不宜使用產生對T2的distinct檢索而導致系統開支巨大的IN操作,反之當T1表數據量巨大(不受索引影響)而T2表數據較少且索引良好則不宜使用引起T1全表掃描的EXISTS操作

No11:NOT IN, NOT EXIST

在子查詢中,NOT IN子句將執行一個內部的排序和合並. 無論在哪種情況下,NOT IN都是最低效的 (因爲它對子查詢中的表執行了一個全表遍歷). 爲了避免使用NOT IN ,我們可以把它改寫成外連接(Outer Joins)或NOT EXISTS.

No12:使用表連接連替換EXIST

通常來說 , 採用表連接的方式比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’ ;

No13:用索引提高效率

1.索引基礎

索引是表的一個概念部分,用來提高檢索數據的效率. 實際上,ORACLE使用了一個複雜的自平衡B-tree結構. 通常,通過索引查詢數據比全表掃描要快. 當ORACLE找出執行查詢和Update語句的最佳路徑時, ORACLE優化器將使用索引. 同樣在聯結多個表時使用索引也可以提高效率. 另一個使用索引的好處是,它提供了主鍵(primary key)的唯一性驗證.
除了那些LONG或LONG RAW數據類型, 你可以索引幾乎所有的列. 通常, 在大型表中使用索引特別有效. 當然,你也會發現, 在掃描小表時,使用索引同樣能提高效率.
雖然使用索引能得到查詢效率的提高,但是我們也必須注意到它的代價. 索引需要空間來
存儲,也需要定期維護, 每當有記錄在表中增減或索引列被修改時, 索引本身也會被修改. 這意味着每條記錄的INSERT , DELETE , UPDATE將爲此多付出4 , 5 次的磁盤I/O . 因爲索引需要額外的存儲空間和處理,那些不必要的索引反而會使查詢反應時間變慢.
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訪問表的操作.

2.存在下面情況的SQL,不會用到索引

 
  存在數據類型隱形轉換的,如:
         select * from staff_member where staff_id=’123’;
  列上有數學運算的,如:
         select * from staff_member where salary*2<10000;
  使用不等於(<> )運算的,如:
         select * from staff_member where dept_no<>2001;
記住, 索引只能告訴你什麼存在於表中, 而不能告訴你什麼不存在於表中.  
使用substr字符串函數的,如:
  select * from staff_member where substr(last_name,1,4)=’FRED’;
 ‘%’通配符在第一個字符的,如:
  select * from staff_member where first_name like ‘%DON’;
字符串連接(||)的,如:
  select * from staff_member where first_name||’’=’DONALD’
       避免在索引中使用任何可以爲空的列,ORACLE將無法使用該索引 .對於單列索引,如果列包含空值,索引中將不存在此記錄. 對於複合索引,如果每個列都爲空,索引中同樣不存在此記錄. 如果至少有一個列不爲空,則記錄存在於索引中.
       通常, 我們要避免在索引列上使用NOT, NOT會產生在和在索引列上使用函數相同的
影響. 當ORACLE”遇到”NOT,他就會停止使用索引轉而執行全表掃描.
 
 
如果一定要對使用函數的列啓用索引, ORACLE新的功能: 基於函數的索引(Function-Based Index) 也許是一個較好的方案.
 CREATE INDEX EMP_I ON EMP (UPPER(ename)); /*建立基於函數的索引*/
 SELECT * FROM emp WHERE UPPER(ename) = ‘BLACKSNAIL’; /*將使用索引*/

3.多個索引情況下的選擇

當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
當ORACLE無法判斷索引的等級高低差別,優化器將只使用一個索引,它就是在WHERE子句中被列在最前面的.
 

4.自動選擇索引

如果表中有兩個以上(包括兩個)索引,其中有一個唯一性索引,而其他是非唯一性.
在這種情況下,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

5.等式比較和範圍比較

     當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
 
3.組合索引總是使用索引的第一個列
如果索引是建立在多個列上, 只有在它的第一個列(leading column)被where子句引用時,優化器纔會選擇使用該索引.

6.CBO下使用更具選擇性的索引

基於成本的優化器(CBO, Cost-Based Optimizer)對索引的選擇性進行判斷來決定索引的使用是否能提高效率.
如果索引有很高的選擇性, 那就是說對於每個不重複的索引鍵值,只對應數量很少的記錄.
比如, 表中共有100條記錄而其中有80個不重複的索引鍵值. 這個索引的選擇性就是80/100 = 0.8 . 選擇性越高, 通過索引鍵值檢索出的記錄就越少.
如果索引的選擇性很低, 檢索數據就需要大量的索引範圍查詢操作和ROWID 訪問表的
操作. 也許會比全表掃描的效率更低.
下列經驗請參閱:
a.       如果檢索數據量超過30%的表中記錄數.使用索引將沒有顯著的效率提高.
b.       在特定情況下, 使用索引也許會比全表掃描慢, 但這是同一個數量級上的
區別. 而通常情況下,使用索引比全表掃描要塊幾倍乃至幾千倍!
 

7.用UNION (ALL)替換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)

8.用>=替代>

DEPT>3和DEPT>=4兩者的區別在於, 前者DBMS將直接跳到第一個DEPT等於4的記錄而後者將首先定位到DEPTNO=3的記錄並且向前掃描到第一個DEPT大於3的記錄.  

No14:用UNION ALL 替換UNION ( 如果有可能的話)

 
當SQL語句需要UNION兩個查詢結果集合時,這兩個結果集合會以UNION-ALL的方式被合併, 然後在輸出最終結果前進行排序.
如果用UNION ALL替代UNION, 這樣排序就不是必要了. 效率就會因此得到提高.

No15:使用提示(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)

No16:避免使用耗費資源的操作

帶有DISTINCT,UNION,MINUS,INTERSECT,ORDER BY的SQL語句會啓動SQL引擎
執行耗費資源的排序(SORT)功能. DISTINCT需要一次排序操作, 而其他的至少需要執行兩次排序.
例如,一個UNION查詢,其中每個查詢都帶有GROUP BY子句, GROUP BY會觸發嵌入排序(NESTED SORT) ; 這樣, 每個查詢需要執行一次排序, 然後在執行UNION時, 又一個唯一排序(SORT UNIQUE)操作被執行而且它只能在前面的嵌入排序結束後才能開始執行. 嵌入的排序的深度會大大影響查詢的效率.
通常, 帶有UNION, MINUS , INTERSECT的SQL語句都可以用其他方式重寫.
    如果你的數據庫的SORT_AREA_SIZE調配得好, 使用UNION , MINUS, INTERSECT也是可以考慮的, 畢竟它們的可讀性很強

No17:分離表和索引

總是將你的表和索引建立在不同的表空間內(TABLESPACES). 決不要將不屬於ORACLE內部系統的對象存放到SYSTEM表空間裏. 同時,確保數據表空間和索引表空間置與不同的硬盤控制卡控制的硬盤上.

No18:排序發生的情況

 
  SQL中包含group by 子句
  SQL 中包含order by 子句
  SQL 中包含 distinct 子句
  SQL 中包含 minus 或 union操作
  創建索引時
這些情況慢。

No19:execute immediate, DBMS_SQL

動態SQL中,儘量多用execute immediate,而少用DBMS_SQL,前者綜合效率優於後者

No20:用like替換和substr


對於‘like’和‘substr’,其效率並沒有多大分別。但是,當所搜索的值不存在時,使用‘like’的速度明顯大於‘substr’。
所以:select  *  from  a  where  substr(a1,1,4) = '5378'  可以用like替代
select  *  from  a  where  a1  like  ‘5378%’

No20:DML語句優化

       1.如果有可能的話truncate 替代delete
       2.大表的刪除轉化爲對剩餘部分建表,truncate原表然後將新建的表改名爲原表.
       3.Update多列的時候,儘量不要用多個set;如:
UPDATE EMP
           SET (EMP_CAT, SAL_RANGE)
             = (SELECT MAX(CATEGORY) , MAX(SAL_RANGE)
             FROM EMP_CATEGORIES)
           WHERE EMP_DEPT = 0020;
       4.如果有索引,刪除索引後執行操作,操作完成後重建索引。
       5.環境允許的話使用並行

No21:使用並行

hint /*+ parallel(tablename,parallel-degree)*/
調整並行執行的目的是:最大地發揮硬件的能力。如果你有一個高性能的系統,有高優先的SQL語句在運行,則並行語句就可以使用所有有效的資源。Oracle可以執行的下面的並行:
 
l       並行查詢;
l       並行DML(包括 INSERT, UPDATE, DELETE; APPEND提示,並行索引掃描);
l       並行 DDL;
如果你的系統缺少以下這些特點,則並行可能不會有多大改善。
l       對稱多處理器(SMP), 集羣或強大的並行系統;
l       有效的I/O帶寬;
l       低利用的或閒置的CPU(如CPU使用小於30%);
l       對附加的內存無效,如分類、哈西索引及I/O緩衝區等。
如果指定的並行度大於實際可用的資源(硬件資源>parallel_max_server>你指定的並行度),將會使用最大的可用資源的並行度來處理。
如果多人同時使用並行, sum(parallel_degree)>parallel_max_server ;可能產生等待使效率下降。

 

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