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語句,在第一次解析之後, ORACLE將SQL語句存放在內存中。這塊位於系統全局區域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表的交集。
5. WHERE子句中的連接順序。
ORACLE採用自下而上的順序解析WHERE子句,根據這個原理,表之間的連接必須寫在其他WHERE條件之前, 那些可以過濾掉最大數量記錄的條件必須寫在WHERE子句的末尾。
例如:
(低效,執行時間156.3秒)
6. SELECT子句中避免使用 ‘ * ’
當你想在SELECT子句中列出所有的COLUMN時,使用動態SQL列引用 ‘*’ 是一個方便的方法。不幸的是,這是一個非常低效的方法。實際上,ORACLE在解析的過程中, 會將‘*’ 依次轉換成所有的列名, 這個工作是通過查詢數據字典完成的, 這意味着將耗費更多的時間。
7. 減少訪問數據庫的次數
當執行每條SQL語句時, ORACLE在內部執行了許多工作: 解析SQL語句, 估算索引的利用率, 綁定變量 , 讀數據塊等等。 由此可見, 減少訪問數據庫的次數 , 就能實際上減少ORACLE的工作量。
例如,以下有三種方法可以檢索出僱員號等於0342或0291的職員。
方法1 (最低效)
方法2 (次低效)
方法3 (高效)
注意:
在SQL*Plus , SQL*Forms和Pro*C中重新設置ARRAYSIZE參數, 可以增加每次數據庫訪問的檢索數據量 ,建議值爲200.
8. 使用DECODE函數來減少處理時間
使用DECODE函數可以避免重複掃描相同記錄或重複連接相同的表。
例如:
你可以用DECODE函數高效地得到相同結果
類似的,DECODE函數也可以運用於GROUP BY 和ORDER BY子句中。
9. 整合簡單,無關聯的數據庫訪問
如果你有幾個簡單的數據庫查詢語句,你可以把它們整合到一個查詢中(即使它們之間沒有關係)
例如:
上面的3個查詢可以被合併成一個:
SELECT E.NAME , D.NAME , C.NAME
(譯者按: 雖然採取這種方法,效率得到提高,但是程序的可讀性大大降低,所以讀者還是要權衡之間的利弊)
10. 刪除重複記錄
最高效的刪除重複記錄方法 ( 因爲使用了ROWID)
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子句限制記錄的數目,那就能減少這方面的開銷。
例如:
低效:
高效
(譯者按: HAVING 中的條件一般用於對一些集合函數的比較,如COUNT() 等等。 除此而外,一般的條件應該寫在WHERE子句中)
15. 減少對錶的查詢
在含有子查詢的SQL語句中,要特別注意減少對錶的查詢。
例如:
低效
高效
Update 多個Column 例子:
低效:
高效:
UPDATE EMP
SET (EMP_CAT, SAL_RANGE)
= (SELECT MAX(CATEGORY) , MAX(SAL_RANGE)
FROM EMP_CATEGORIES)
WHERE EMP_DEPT = 0020;
16. 通過內部函數提高SQL效率。
通過調用下面的函數可以提高效率。
(譯者按: 經常在論壇中看到如 ‘能不能用一個SQL寫出…。’ 的貼子, 殊不知複雜的SQL往往犧牲了執行效率。 能夠掌握上面的運用函數解決問題的方法在實際工作中是非常有意義的)
17. 使用表的別名(Alias)
當在SQL語句中連接多個表時, 請使用表的別名並把別名前綴於每個Column上。這樣一來,就可以減少解析的時間並減少那些由Column歧義引起的語法錯誤。
(譯者注: Column歧義指的是由於SQL中不同的表具有相同的Column名,當SQL語句中出現這個Column時,SQL解析器無法判斷這個Column的歸屬)
18. 用EXISTS替代IN
在許多基於基礎表的查詢中,爲了滿足一個條件,往往需要對另一個表進行聯接。在這種情況下, 使用EXISTS(或NOT EXISTS)通常將提高查詢的效率。
低效:
高效:
(譯者按: 相對來說,用NOT EXISTS替換NOT IN 將更顯著地提高效率,下一節中將指出)
19. 用NOT EXISTS替代NOT IN
在子查詢中,NOT IN子句將執行一個內部的排序和合並。 無論在哪種情況下,NOT IN都是最低效的 (因爲它對子查詢中的表執行了一個全表遍歷)。 爲了避免使用NOT IN ,我們可以把它改寫成外連接(Outer Joins)或NOT EXISTS.
例如:
爲了提高效率。改寫爲:
(方法一: 高效)
(方法二: 最高效)
20. 用表連接替換EXISTS
通常來說 , 採用表連接的方式比EXISTS更有效率
(更高效)
(譯者按: 在RBO的情況下,前者的執行路徑包括FILTER,後者使用NESTED LOOP)
21. 用EXISTS替換DISTINCT
當提交一個包含一對多表信息(比如部門表和僱員表)的查詢時,避免在SELECT子句中使用DISTINCT. 一般可以考慮用EXIST替換
例如:
低效:
高效:
EXISTS 使查詢更爲迅速,因爲RDBMS核心模塊將在子查詢的條件一旦滿足後,立刻返回結果。
22. 識別‘低效執行’的SQL語句
用下列SQL工具找出低效SQL:
SELECT EXECUTIONS , DISK_READS, BUFFER_GETS,
(譯者按: 雖然目前各種關於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 功能比較方便。
舉例:
通過以上分析,可以得出實際的執行步驟是:
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)
注: 目前許多第三方的工具如TOAD和ORACLE本身提供的工具如OMS的SQL Analyze都提供了極其方便的EXPLAIN PLAN工具。也許喜歡圖形化界面的朋友們可以選用它們。
25. 用索引提高效率
索引是表的一個概念部分,用來提高檢索數據的效率。 實際上,ORACLE使用了一個複雜的自平衡B-tree結構。通常,通過索引查詢數據比全表掃描要快。 當ORACLE找出執行查詢和Update語句的最佳路徑時, ORACLE優化器將使用索引。同樣在聯結多個表時使用索引也可以提高效率。 另一個使用索引的好處是,它提供了主鍵(primary key)的唯一性驗證。
除了那些LONG或LONG RAW數據類型, 你可以索引幾乎所有的列。 通常, 在大型表中使用索引特別有效。 當然,你也會發現, 在掃描小表時,使用索引同樣能提高效率。
雖然使用索引能得到查詢效率的提高,但是我們也必須注意到它的代價。 索引需要空間來存儲,也需要定期維護,每當有記錄在表中增減或索引列被修改時, 索引本身也會被修改。 這意味着每條記錄的INSERT , DELETE , UPDATE將爲此多付出4 , 5 次的磁盤I/O . 因爲索引需要額外的存儲空間和處理,那些不必要的索引反而會使查詢反應時間變慢。
譯者按:定期的重構索引是有必要的。
ALTER INDEX <INDEXNAME> REBUILD <TABLESPACENAME>
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子句中, 如果索引列所