Oracle語句優化的十九種方式

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

二. Where字句中的鏈接順序

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 *

當你想在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替代IN

在許多基於基礎表的查詢中,爲了知足一個條件,每每須要對另外一個表進行鏈接,在這種狀況下,使用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 EXISTS替代NOT IN

在子查詢中,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

一般來講,採用錶鏈接的方式比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不惟一。

十一.用EXISTS替換DISTINCT

當提交一個包含一對多表信息(好比部門表和僱員表)的查詢時,避免在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替換OR

一般狀況下,用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」;

十六.IN 和OR

下面兩個查詢等價:

SELECT ……
FROM LOCATION
WHERE LOC_ID = 10
OR LOC_ID = 20;

SELECT …… FROM LOCATION
WHERE LOC_IN  IN(10,20);

在9i後,ORACLE能夠根據優化模式選擇合併方式或直接IN檢索方式。

十七.避免在索引列上使用IS NULL和IS NOT NULL

對於單列索引,若是列包含空值,索引中將不存在此記錄。

對於複合索引,若是單個列都爲空,索引中一樣不存在此記錄。若是至少有一個列不爲空。則記錄存在於索引中。

由於空值不存在於索引中,因此WHERE字句中對索引列進行空值比較將使ORACLE停用該索引。

下面的例子不使用索引:

SELECT ……
FROM DEPARTMENT
WHERE DEPT_CODE IS NOT NULL;

能夠改編以下(若是爲字符,可根據狀況寫爲>’a’);

SELECT ……
FROM DEPARTMENT
WHERE DEPT_CODE >= 0;

十八.用UNION-ALL替換UNION

當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語句均可以用其餘方式重寫。

相關文章
相關標籤/搜索