Oracle語句優化的十九種方式sql
孟飛陽 2012-02-09函數
該方法只在基於規則的優化器中有效,基於代價時,ORACLE會根據表的物理大小,索引的狀態,而後選用花費最低的執行路徑。ORACLE的解析器按照從右到左的順序處理FROM字句中的表名,所以FROM字句寫在最後的表將被最早處理。優化
在FROM字句中的包含多個表的狀況下,必須選擇記錄條數最少的表做爲基礎表(放在最後)。spa
若是有3個以上的錶鏈接查詢,那就須要選擇交叉表(intersection table)做爲基礎表,交叉表是指哪一個被其餘表所引用的表。code
例:排序
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
ORACLE採用自下而上的順序解析where字句。資源
ORACLE總會在能使用索引的時候使用索引(除非表特別小),但若是有多個索引可供使用時,可能會由於順序的不一樣致使效率的不一樣。考慮下面的SQL語句:io
SELECT …… FROM EMP_REC WHERE EMAIL = ‘liu.yuzhou’ And DEPT_NO = ‘010201’
若是在(DEPT_NO,EMAIL)上有聯合索引,且DEPT_NO上有索引,則使用聯合索引;若是在DEPT_NO和EMAIL上都有索引,但EMAIL上是惟一索引,則使用EMAIL上的索引;若是在DEMP_NO和EMAIL都有索引,且都爲非惟一性索引,則先根據EMAIL檢索記錄,再根據DEPT_NO檢索記錄,而後取他們的交集。table
因此,咱們在寫SQL語句時,就應該考慮索引的使用狀況。
若是沒法使用索引,ORACLE會執行全表掃描,咱們應將限制性強的條件放在後面。如咱們知道一個部門大約有500人,而一個EMAIL大約對應5人,則咱們應該將EMAIL=’liu.yuzhou’的條件放在後面。這樣能夠避免部分記錄的二次比對。
當你想在SELECT子句中列出全部的COLUMN時,使用’SELECT * ‘是一個方便的方法。不幸的是,這是一個低效的方法。實際上,ORACLE在解析的過程當中,會將’* ‘依次轉換成全部的列名,這個工做是經過查詢數據字典完成的,這意味着將耗費更多的時間。
最高效的刪除重複記錄方法(由於使用了ROWID)
DELECT FROM EMP E WHERE E.ROWID > (SELECT MIN(X.ROWID) FROM EMP X WHERE X.EMP_NO = E.EMP_NO);
例1:
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)
例2:
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_CAEGORIES) WHERE EMP_DEPT = 0020;
當在SQL語句中鏈接多個表時,請使用表的別名並把別名前綴於每一個Column上。這樣一來,就能夠減小解析的時間並減小那些由Column歧義引發的語法錯誤。
Column歧義指的是因爲SQL中不一樣的表具備相同的Column名,當SQL語句出如今這個Column時,SQL解析器沒法判斷這個Column的歸屬。
在許多基於基礎表的查詢中,爲了知足一個條件,每每須要對另外一個表進行鏈接,在這種狀況下,使用EXISTS(或NOT EXISTS)一般將提升查詢的效率。
例:
SELECT * FROM EMP(基礎表) WHERE EMP_NO > 0 AND DEPTNO IN(SELECT DEPTNO FROM DEPT WHERE LOC = ‘MELB’) 可改編爲: SELECT * FROM EMP(基礎表) WHERE EMP_NO > 0 AND EXISTS(SELECT ‘X’ FROM DEPT WHERE DEPT.DEPTNO = EMP.DEPTNO AND LOC = ‘MELB’
在子查詢中,NOT IN字句將執行一個內部的排序和合並。不管在哪一種狀況下,NOT IN都是最低效的(由於它對子查詢中的表執行了一個全表遍歷)。爲了不使用NOT IN,咱們能夠把它改寫成外鏈接(Out 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_NO(+) 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’);
一般來講,採用錶鏈接的方式比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’;
可是不少狀況下咱們沒法將EXISTS改編爲鏈接,如DEPT_NO不惟一。
當提交一個包含一對多表信息(好比部門表和僱員表)的查詢時,避免在SELECT字句中使用DISTINCT。通常能夠考慮用EXIST替換。
EXISTS使用查詢更爲迅速,由於REBMS核心模塊將在子查詢的條件一旦知足後,馬上返回結果。
例:找出有職員的部門
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);
索引是表的一個概念部門,用來提升檢索數據的效率。一般,經過索引查詢數據比全表掃描的要快。
一樣在連結多個表時使用索引也能夠提升效率。另外一個使用索引的好處是,它提供了主鍵(primary Key)的惟一性驗證。
使用索引時也必須注意到它的代價。索引須要空間存儲,也須要按期維護,每當有記錄在表中增減或索引列被修改時,索引自己也會修改。這意味着每條記錄的INSERT,DELECT,UPDATE將爲此多付出4,5次的磁盤I/O。由於索引須要額外的存儲空間和處理,那些沒必要要的索引反而會使查詢反應時間變慢。
ORACLE對索引有兩種訪問模式:
索引惟一掃描(INDEX UNIQUE SCAN)
索引範圍查詢(INDEX RANGE SCAN)
適用於兩種狀況:
1) 基於一個範圍的檢索
2) 基於非惟一性索引的檢索
在前面已經講了一些使用索引的狀況,下面再補充幾種:
當WHERE字句中有多個索引列,且包含非’=’號時,ORACLE會放棄使用非’=’號的索引:
例:
DEPTNO上有一個非惟一性索引,EMP_CAT也有一個非惟一性索引
SELECT ENAME FROM EMP
WHERE DEPTNO > 20 AND EMP_CAT = ‘A’;
這裏只有EMP_CAT索引被用到,而後全部的記錄將逐條與DEPTNO條件進行比較,執行路徑以下:
TABLE ACCESS BY ROWIDON EMP
INDEX RANGESCAN ON CAT_IDX
這是由於DEPTNO > 20的條件可能會檢索出大量記錄,而EMP_CAT = ‘A’可能只檢索出少許記錄。合併操做好比上面的方式划算。
當WHERE字句中有多個索引列,且都爲非’=’號時,ORACLE將只使用一個索引:
例:
DEPTNO上有一個非惟一性索引,EMP_CAT也有一個非惟一性索引。
SELECT ENAME FROM EMP
WHERE DEPTNO > 20 AND EMP_CAT > ‘A’;
這裏,ORACLE只會用到其中一個索引(用哪一個視優化模式,統計信息而定),執行路徑以下(假使使用DEPTNO上的索引);
TABLE ACCESS BY ROWID ON EMP
INDEX RANGE SCAN ON DEPT_IDX
若是兩個或以上索引具備相同的等級,而咱們只想使用其中的一個(經過它,檢索出的記錄數量少),咱們可使用下面的方法:
SELECT ENAME FROM EMP WHERE EMPNO = 7935 AND DEPTNO = 10 AND EMP_TYPE = ‘A’;
若是咱們只想用到EMPNO上的索引(相對另外兩個條件記錄數量較少,作合併不划算),則可改編爲:
SELECT ENAME FROM EMP WHERE EMPNO = 7935 AND DEPTNO + 0 = 10 /*DEPTNO上的索引將不會使用*/
一樣,若是咱們想使用某個列上的索引,則不能對此列作運算,例:
SELECT …… FROM DEPT WHERE SAL * 12 > 25000; 不能使用SAL列上的索引,可改編爲: SELECT …… FROM DEPT WHERE SAL > 25000/12;
若是DEPTNO上有一個索引,則:
SELECT * FROM EMP WHERE DEPTNO >= 4 比下面的語句更有效率: SELECT * FROM EMP WHERE DEPTNO > 3;
二者的區別在於,前者將直接跳到第一個DEPT等於4的記錄然後者將首先定位到DEPTNO = 3的記錄而且想前掃描到第一個DEPT大於3的記錄。
一般狀況下,用UNION替換WHERE字句中的OR將會起到較好的效果。對索引列使用OR將形成全表掃描。注意,以上規則只針對多個索引列有效,若是有列沒有被索引,查詢效率可能會由於沒有選擇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」;
下面兩個查詢等價:
SELECT …… FROM LOCATION WHERE LOC_ID = 10 OR LOC_ID = 20; SELECT …… FROM LOCATION WHERE LOC_IN IN(10,20);
在9i後,ORACLE能夠根據優化模式選擇合併方式或直接IN檢索方式。
對於單列索引,若是列包含空值,索引中將不存在此記錄。
對於複合索引,若是單個列都爲空,索引中一樣不存在此記錄。若是至少有一個列不爲空。則記錄存在於索引中。
由於空值不存在於索引中,因此WHERE字句中對索引列進行空值比較將使ORACLE停用該索引。
下面的例子不使用索引:
SELECT …… FROM DEPARTMENT WHERE DEPT_CODE IS NOT NULL; 能夠改編以下(若是爲字符,可根據狀況寫爲>’a’); SELECT …… FROM DEPARTMENT WHERE DEPT_CODE >= 0;
當SQL語句須要UNION兩個查詢結果集合時,這兩個結果集合會以UNION-ALL的方式被合併,而後在輸出最終結果前進行排序,並將重複記錄過濾掉。
若是用UNION-ALL替代UNION,這樣排序就不是必要的了,效率會所以獲得提升。
須要注意的是,UNION-ALL將重複輸出兩個結果集合中相同記錄。所以,仍是要從業務需求分析使用UNION-ALL的可行性。
‘!=’將不使用索引。索引只能告訴你什麼存在於表中,而不能告訴你什麼不存在於表中。
基於成本優化器(CBO,Cost-Based Optimizer)對索引的選擇性進行判斷來決定索引的使用是否能提升效率。
若是索引有很高的選擇性,那就是說對於每一個不重複的索引鍵值,只對應數量不多的記錄。
好比,表中共有100條記錄而其中有80個不重複的索引鍵值,這個索引的選擇性就是80/100=0.8,選擇性越高,經過索引鍵值檢索出的記錄就越少。
若是索引的選擇性很低,檢索數據就須要大量的索引範圍查詢操做和ROWID訪問表的操做,也許會比全表掃描的效率更低。
帶有DISTINCT,UNION,MINUS,INTERSECT,ORDER BY的SQL語句會執行耗費資源的排序(SORT)功能。DISTINCT須要一次排序操做,而其餘的至少須要執行兩次排序。
例如。一個UNION查詢,其中每一個查詢都帶有GROUP BY字句,GROUP BY會觸發嵌入排序(NESTED SORT);這樣,每一個查詢須要執行一次排序,而後在執行UNION時,有一個惟一排序(SORT UNIQUE)操做被執行並且它只能在前面的嵌入排序結束後才能開始執行。嵌入順序的深度會大大影響查詢的效率。
一般,帶有UNION,MINUS,INTERSECT的SQL語句均可以用其餘方式重寫。