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這四種方式,也就是我們以上所提及的。如下我解釋一下:
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 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;