1. 程式人生 > >Oracle優化器的優化方式和優化模式

Oracle優化器的優化方式和優化模式

Oracle在執行一個SQL之前,首先要分析一下語句的執行計劃,然後再按執行計劃去執行。分析語句的執行計劃的工作是由優化器(Optimizer) 來完成的。不同的情況,一條SQL可能有多種執行計劃,但在某一時點,一定只有一種執行計劃是最優的,花費時間是最少的。相信你一定會用Pl/sql Developer、Toad等工具去看一個語句的執行計劃,不過你可能對Rule、Choose、First rows、All rows這幾項有疑問,因為我當初也是這樣的,那時我也疑惑為什麼選了以上的不同的項,執行計劃就變了?

1、優化器的優化方式

Oracle的優化器共有兩種的優化方式,即基於規則的優化方式(Rule-Based Optimization,簡稱為RBO)和基於代價的優化方式(Cost-Based Optimization,簡稱為CBO)。

A、RBO方式:
優化器在分析SQL語句時,所遵循的是Oracle內部預定的一些規則。比如我們常見的,當一個where子句中的一列有索引時去走索引。
在ORACLE7之前,主要是使用基於規則的優化器。ORACLE在基於規則的優化器中採用啟發式的方法(Heuristic Approach)或規則(Rules)來生成執行計劃。例如,如果一個查詢的where條件(where clause)包含一個謂詞(predicate,其實就是一個判斷條件,如”=”, “>”, ”<”等),而且該謂詞上引用的列上有有效索引,那麼優化器將使用索引訪問這個表,而不考慮其它因素,如表中資料的多少、表中資料的易變性、索引的 可選擇性等。此時資料庫中沒有關於表與索引資料的統計性描述,如表中有多上行,每行的可選擇性等。優化器也不考慮例項引數,如multi block i/o、可用排序記憶體的大小等,所以優化器有時就選擇了次優化的計劃作為真正的執行計劃,導致系統性能不高。
如,對於
select * from emp where deptno = 10;
這個查詢來說,如果是使用基於規則的優化器,而且deptno列上有有效的索引,則會通過deptno列上的索引來訪問emp表。在絕大多數情況下,這是比較高效的,但是在一些特殊情況下,使用索引訪問也有比較低效的時候,現舉例說明:
1) emp表比較小,該表的資料只存放在幾個資料塊中。此時使用全表掃描比使用索引訪問emp表反而要好。因為表比較小,極有可能資料全在記憶體中,所以此時做 全表掃描是最快的。而如果使用索引掃描,需要先從索引中找到符合條件記錄的rowid,然後再一一根據這些rowid從emp中將資料取出來,在這種條件 下,效率就會比全表掃描的效率要差一些。
2) emp表比較大時,而且deptno = 10條件能查詢出表中大部分的資料如(50%)。如該表共有4000萬行資料,共放在有500000個數據塊中,每個資料塊為8k,則該表共有約4G,則 這麼多的資料不可能全放在記憶體中,絕大多數需要放在硬碟上。此時如果該查詢通過索引查詢,則是你夢魘的開始。 db_file_multiblock_read_count引數的值200。如果採用全表掃描,則需要 500000/db_file_multiblock_read_count=500000/200=2500次I/O。但是如果採用索引掃描,假設 deptno列上的索引都已經cache到記憶體中,所以可以將訪問索引的開銷忽略不計。因為要讀出4000萬x 50% = 2000萬資料,假設在讀這2000萬資料時,有99.9%的命中率
(?),則還是需要20000次I/O,比上面的全表掃描需要的2500次多多了,所以在這 種情況下,用索引掃描反而效能會差很多。在這樣的情況下,用全表掃描的時間是固定的,但是用索引掃描的時間會隨著選出資料的增多使查詢時間相應的延長。
上面是枯燥的假設資料,現在以具體的例項給予驗證:
環境: oracle 817 + linux + 陣列櫃,表SWD_BILLDETAIL有3200多萬資料;
表的id列、cn列上都有索引
經檢視執行計劃,發現執行select count(id) from SWD_BILLDETAIL;使用全表掃描,執行完用了大約1.50分鐘(4次執行取平均,每次分別為1.45 1.51 2.00 1.46)。而執行select count(id) from SWD_BILLDETAIL where cn <'6';卻用了2個小時還沒有執行完,經分析該語句使用了cn列上的索引,然後利用查詢出的rowid再從表中查詢資料。我為什麼不使用 select count(cn) from SWD_BILLDETAIL where cn <'6';呢?後面在分析執行路徑的索引掃描時時會給出說明。
下面就是基於規則的優化器使用的執行路徑與各個路徑對應的等級:
RBO Path 1: Single Row by Rowid(等級最高)
RBO Path 2: Single Row by Cluster Join
RBO Path 3: Single Row by Hash Cluster Key with Unique or Primary Key
RBO Path 4: Single Row by Unique or Primary Key
RBO Path 5: Clustered Join
RBO Path 6: Hash Cluster Key
RBO Path 7: Indexed Cluster Key
RBO Path 8: Composite Index
RBO Path 9: Single-Column Indexes
RBO Path 10: Bounded Range Search on Indexed Columns
RBO Path 11: Unbounded Range Search on Indexed Columns
RBO Path 12: Sort Merge Join
RBO Path 13: MAX or MIN of Indexed Column
RBO Path 14: ORDER BY on Indexed Column
RBO Path 15: Full Table Scan(等級最低)
上面的執行路徑中,RBO認為越往下執行的代價越大,即等級越低。在RBO生成執行計劃時,如果它發現有等級高的執行路徑可用,則肯定會使用 等級高的路徑,而不管任何其它影響效能的元素,即RBO通過上面的路徑的等級決定執行路徑的代價,執行路徑的等級越高,則使用該執行路徑的代價越小。如上 面2個例子所述,如果使用RBO,則肯定使
用索引訪問表,也就是選擇了比較差的執行計劃,這樣會給資料庫效能帶來很大的負面影響。為了解決這個問題,從ORACLE 7開始oracle引入了基於代價的優化器,下面給出了介紹。

B、CBO方式:
依詞義可知,它是看語句的代價(Cost)了,這裡的代價主要指Cpu和記憶體。  
      優化器在 判斷是否用這種方式時,主要參照的是表及索引的統計資訊。統計資訊給出表的大小 、有多少行、每行的長度等資訊。這些統計資訊起初在庫內是沒有的,是你在做analyze後才出現的,很多的時侯過期統計資訊會令優化器做出一個錯誤的執行 計劃,因些我們應及時更新這些資訊。在Oracle8及以後的版本,Oracle列推薦用CBO的方式。

      我們要明瞭,不一定走索引就是優的 ,比如一個表只有兩行資料,一次IO就可以完成全表的檢索,而此時走索引時則需要兩次IO,這時對這個表做全表掃描(full table scan)是最好的。Oracle把一個代價引擎(Cost Engine)整合到資料庫核心中,用來估計每個執行計劃需要的代價,該代價將每個執行計劃所耗費的資源進行量化,從而CBO可以根據這個代價選擇出最優 的執行計劃。
    一個查詢耗費的資源可以被分成3個基本組成部分:I/O代價、CPU代價、network代價。
    
I/O代價是將資料從磁碟讀入記憶體所需的代 價。訪問資料包括將資料檔案中資料塊的內容讀入到SGA的資料快取記憶體中,在一般情況下,該代價是處理一個查詢所需要的最主要代價,所以我們在優化時,一 個基本原則就是降低查詢所產生的I/O總次數。
     CPU代價是處理在記憶體中資料所需要的代價,如一旦資料被讀入記憶體,則我們在識別出我們需要的資料後,在這 些資料上執行排序(sort)或連線(join)操作,這需要耗費CPU資源。
    對於需要訪問跨節點(即通常說的伺服器)資料庫上資料的查詢來說,存在network代價,用來量化傳輸操作耗費的資源。查詢遠端表的查詢或執行分散式連線的查詢會在network代價方面花費比較大。
     在使用CBO時,需要有表和索引的統計資料(分析資料)作為基礎資料,有了這些資料,CBO才能為各個執行計劃計算出相對準確的代價,從而使 CBO選擇最佳的執行計劃。所以定期的對錶、索引進行分析是絕對必要的,這樣才能使統計資料反映資料庫中的真實情況。否則就會使CBO選擇較差的執行計 劃,影響資料庫的效能。分析操作不必做的太頻繁,一般來說,每星期一次就足夠了。切記如果想使用CBO,則必須定期對錶和索引進行分析。
     對於分析用的命令,隨著資料庫版本的升級,用的命令也發生了變換,在oracle 8i以前,主要是用ANALYZE命令。在ORACLE 8I以後,又引入了DBMS_STATS儲存包來進行分析。幸運的是從ORACLE 10G以後,分析工作變成自動的了,這減輕的DBA的負擔,不過在一些特殊情況下,還需要一些手工分析。
如果採用了CBO優化器,而沒有對錶和索引進行分析,沒有統計資料,則ORACLE使用預設的統計資料(至少在ORACLE 9I中是這樣),這可以從oracle的文件上找到。使用的預設值肯定與系統的實際統計值不一致,這可能會導致優化器選擇錯誤的執行計劃,影響資料庫的性 能。
要注意的是:雖然CBO的功能隨著ORACLE新版本的推出,功能越來越強,但它不是能包治百病的神藥,否則就不再需要DBA了,那我就慘 了!!!實際上任何一個語句,隨著硬體環境與應用資料的不同,該語句的執行計劃可能需要隨之發生變化,這樣才能取得最好的效能。所以有時候不在具體的環境 下而進行SQL效能調整是徒勞的。
在ORACLE8I推出的時候,ORACLE極力建議大家使用CBO,說CBO有種種好處,但是在那是ORACLE開發的應用系統還是使用 基於規則的優化器,從這件事上我們可以得出這樣的結論:1) 如果團隊的資料庫水平很高而且都熟悉應用資料的特點,RBO也可以取得很好的效能。2)CBO不是很穩定,但是一個比較有前途的優化器,Oracle極力 建議大家用是為了讓大家儘快發現它的BUG,以便進一步改善,但是ORACLE為了對自己開發的應用系統負責,他們還是使用了比較熟悉而且成熟的RBO。 從這個事情上給我們的啟發就是:我們在以後的開發中,應該儘量採用我們熟悉並且成熟的技術,而不要一味的採用新技術,一味採用新技術並不一定能開發出好的 產品。幸運的是從ORACLE 10G後,CBO已經足夠的強大與智慧,大家可以放心的使用該技術,因為ORACLE 10G後,Oracle自己開發的應用系統也使用CBO優化器了。而且ORACLE規定,從ORACLE 10G開始,開始廢棄RBO優化器。這句話並不是指在ORACLE 10G中不能使用RBO,而是從ORACLE10G開始開始,不再為RBO的BUG提供修補服務。
在上面的第2個例子中,如果採用CBO優化器,它就會考慮emp表的行數,deptno列的統計資料,發現對該列做查詢會查詢出過多的數 據,並且考慮db_file_multiblock_read_count引數的設定,發現用全表掃描的代價比用索引掃描的代價要小,從而使用全表掃描從 而取得良好的執行效能。

2、優化器的優化模式(Optermizer Mode)

優化模式包括Rule,Choose,First rows,All rows這四種方式,也就是我們以上所提及的。如下我解釋一下:

Rule:不用多說,即走基於規則的方式。

Choolse:這是我們應觀注的,預設的情況下Oracle用的便是這種方式。指的是當一個表或或索引有統計資訊,則走CBO的方式,如果表或索引沒統計資訊,表又不是特別的小,而且相應的列有索引時,那麼就走索引,走RBO的方式。

First Rows:它與Choose方式是類似的,所不同的是當一個表有統計資訊時,它將是以最快的方式返回查詢的最先的幾行,從總體上減少了響應時間。

All Rows:也就是我們所說的Cost的方式,當一個表有統計資訊時,它將以最快的方式返回表的所有的行,從總體上提高查詢的吞吐量。沒有統計資訊則走基於規則的方式。

3、如何設定選用哪種優化模式

◆A、Instance級別

可以通過在init.ora檔案中設定OPTIMIZER_MODE=RULE、OPTIMIZER_MODE=CHOOSE、 OPTIMIZER_MODE=FIRST_ROWS、OPTIMIZER_MODE=ALL_ROWS去選用3所提的四種方式,如果你沒設定 OPTIMIZER_MODE引數則預設用的是Choose這種方式。

使用spfile的情況,使用下面的語句:
alter system set optimizer_mode=choose scope=spfile;

◆B、Sessions級別

通過 ALTER SESSION SET OPTIMIZER_MODE=;來設定。

◆C、語句級別

這些需要用到Hint,比如:

SQL> SELECT /*+ RULE */ a.userid,
2 b.name,
3 b.depart_name
4 FROM tf_f_yhda a,
5 tf_f_depart b
6 WHERE a.userid=b.userid;

4、為什麼有時一個表的某個欄位明明有索引,當觀察一些語的執行計劃確不走索引呢?如何解決呢?

◆A、不走索引大體有以下幾個原因

你在Instance級別所用的是all_rows的方式
你的表的統計資訊(最可能的原因)
你的表很小,上文提到過的,Oracle的優化器認為不值得走索引。

◆B、解決方法

可以修改init.ora中的OPTIMIZER_MODE這個引數,把它改為Rule或Choose,重起資料庫。也可以使用4中所提的Hint.
刪除統計資訊
SQL>analyze table table_name delete statistics;
表小不走索引是對的,不用調的。

5、其它相關

◆A、如何看一個表或索引是否是統計資訊

SQL>SELECT * FROM user_tables
2 WHERE table_name=<table_name>
3 AND num_rows is not null;

SQL>SELECT * FROM user_indexes
2 WHERE table_name=<table_name>
3 AND num_rows is not null;

◆B、假如我們先用CBO的方式,就應當及時去更新表和索引的統計資訊,以免生形不切合實的執行計劃。

SQL> ANALYZE TABLE table_name COMPUTE STATISTICS;
SQL> ANALYZE INDEX index_name ESTIMATE STATISTICS;