天天看點

一個執行計劃異常變更引發的Oracle性能診斷優化

最近有一個oltp應用使用的oracle資料庫突然出現性能問題,dba發現有一些delete語句執行時間驟長,消耗大量系統資源,導緻應用響應時間變長積q。

輔助資訊:

應用已經很久未做過更新上線了。

據開發人員回報,從之前的應用日志看,未出現處理時間逐漸變長的現象。

這是一套rac+dg的環境,11g的版本。

這次突然出現大量執行時間超長的sql語句,是一條删除語句,delete from table where key1=:1 and key2=:2 and ...(省略此案例不會用到的其他條件),應用正常的處理邏輯中都會使用這條語句,是以并發較高,使用了綁定變量,key1和key2字段不是主鍵,但有索引,存在直方圖。

接下來會通過理論和實驗相結合的方式,了解這個問題所需要涉及的一些oracle基礎知識,最後再來分析這個案例。

本文目錄:

一、基礎知識介紹

可能造成sql執行計劃發生改變的一個示例

綁定變量窺探

檢視綁定變量值的幾種方法

rolling invalidation  

聚簇因子(clustering factor)

查詢執行計劃的幾種方法     

awr

ash

sql awr

直方圖

sql profile     

二、案例分析

1、可能造成sql執行計劃發生改變的一個示例

什麼情況下可能造成sql執行計劃發生改變?有很多種情況,這裡抛磚引玉舉一個例子。

實驗: 建立測試表t1,其中name字段設定索引,取值為10000個a和1個b。 

一個執行計劃異常變更引發的Oracle性能診斷優化

我們看下用查詢條件name=’a’的sql使用了什麼執行計劃。

一個執行計劃異常變更引發的Oracle性能診斷優化

再看下使用查詢條件name=’b’的sql用了什麼執行計劃。

一個執行計劃異常變更引發的Oracle性能診斷優化

顯而易見,因為取值為a的記錄占據了10000/10001接近100%的比重,即這查詢條件傳回了幾乎表的所有資料,使用全表掃描的成本一般會小于使用索引的成本,由于table access full會掃描表高水位線以下的資料塊,且為多塊讀,即一次io會讀取多個資料塊,具體資料塊數量取決于參數db_file_multiblock_read_count,而index range scan則是單塊讀,同時若select字段不是索引字段的話,還需要回表,累積起來,io次數就會可能很大,是以相比起來,全表掃描的io可能會遠小于索引掃描。

取值為b的記錄占據了1/10001很小的比重,是以使用索引掃描,直接通路b*tree二叉樹,定位到這一條資料的rowid再回表查詢所有select字段的成本要遠小于掃描整張表資料的成本。

為了證明,可以檢視這兩條sql對應的10053事件,如下是name=’a’的trace,可以看出全表掃描的成本值是49.63,索引掃描的成本值是351.26,全表掃描的成本更低一些。 

一個執行計劃異常變更引發的Oracle性能診斷優化

如下是name=’b’的trace,可以看出全表掃描的成本值是49.40,索引掃描的成本值是2.00,索引掃描的成本值更低一些。

一個執行計劃異常變更引發的Oracle性能診斷優化

這個場景可以看出,oracle的cbo模式會根據字段的取值比重調整對應的執行計劃,無論如何,都會選擇成本值最低的一個執行計劃,這也是cbo優于以前rbo的地方,這裡僅用于實驗,因為一般oltp的應用會使用綁定變量的寫法,不會像上面這種使用常量值的寫法,11g之前,可能帶來的一些負面影響就是綁定變量窺探的作用,即對于使用綁定變量窺探的sql語句,oracle會根據第一次執行使用的綁定變量值來用于以後的執行,即第一次做硬解析的時候,窺探了變量值,之後的軟解析,不再窺視,換句話說,如果上面實驗的sql語句使用了綁定變量,第一次執行時name=’a’,則接下來即使使用name=’b’的sql語句仍會使用全表掃描,不會選擇索引掃描,vice versa。相關的實驗dbsnake的書中會有很詳細的說明,可以參考。11g之後,有了acs自适應遊标的新特性,會根據綁定變量值的情況可以重新生成執行計劃,是以這種問題得到了緩解,當然這些都是有代價的,緩解了綁定變量窺探的副作用,相應地可能會導緻有很多子遊标,具體的算法可以參考dbsanke的書,這兒我就不班門弄斧了。11g預設綁定變量窺探是開啟的,由以下隐藏參數控制。

一個執行計劃異常變更引發的Oracle性能診斷優化

綜上所述,針對這場景,如果值的選擇性顯著影響執行計劃,則綁定變量的使用并不可靠,此時選擇字面值的方式可能會更合适一些,如果值的選擇性幾乎相同,執行計劃不會顯著改變,此時使用綁定變量是最優的選擇,當然前提是oltp系統。

對于多次執行sql語句,執行計劃發生變化的情況可能還有很多,例如11g的新特性cardinality feedback帶來的一些bug,包含直方圖的字段作為查詢條件但統計資訊不準等。

2、綁定變量窺探

首先什麼是綁定變量? 

一條sql語句在解析階段,會根據sql文本對應的哈希值在庫緩存中查找是否有比對的parent cursor,進而找出是否有可重用的解析樹和執行計劃,若沒有則要重新生成一遍,oltp系統中,高并發的sql若每次均需要重複執行這些操作,即所謂的硬解析,消耗會比較大,進而影響系統性能,是以就需要使用綁定變量。綁定變量其實就是一些占位符,用于替換sql文本中具體輸入值,例如以下兩條sql:

select * from t1 where id = 1;

select * from t1 where id = 2;

在oracle看來,是兩條完全不同的sql,即對應sql文本哈希值不同,因為where條件中一個id是1,一個是2,1和2的ascii是不同的,可實際上這兩條sql除了查詢條件不同,其他的文本字元均一緻,盡管如此,這種情況下,oracle還是會重複執行解析的操作,生成各自的遊标。

一個執行計劃異常變更引發的Oracle性能診斷優化

兩條記錄,說明oracle認為這兩條sql是不同。

如果使用綁定變量:

select * from t1 where id = :1;

每次将不同的參數值帶入:1中,語義和上面兩條相同,但對應哈希值可是1個,換句話說,解析樹和執行計劃是可以重用的。

一個執行計劃異常變更引發的Oracle性能診斷優化

使用綁定變量除了以上可以避免硬解析的好處之外,還有其自身的缺陷,就是這種純綁定變量的使用适合于綁定變量列值比較均勻分布的情況,如果綁定變量列值有一些非均勻分布的特殊值,就可能會造成非高效的執行計劃被選擇。

如下是測試表: 

一個執行計劃異常變更引發的Oracle性能診斷優化

其中name列是非唯一索引,name是a的有100000條記錄,name是b的有1條記錄,值分布是不均勻的,上一篇文章中我們使用如下兩條sql做實驗。

select * from t1 where name = 'a';

select * from t1 where name = 'b';

其中第一條使用的是全表掃描,第二條使用了索引範圍掃描,過程和原因上篇文章中有叙述,此處就不再贅述。

如上sql使用的是字面值或常量值作為檢索條件,接下來我們使用綁定變量的方式來執行sql,為了更好地說明,此處我們先關閉綁定變量窺探(預設情況下,是開啟的狀态),他是什麼我們稍後再說。

一個執行計劃異常變更引發的Oracle性能診斷優化

首先a為條件。

一個執行計劃異常變更引發的Oracle性能診斷優化

顯示使用了全表掃描。

再以b為條件。

一個執行計劃異常變更引發的Oracle性能診斷優化

發現仍舊是全表掃描,我們之前知道b值記錄隻有一條,應該使用索引範圍掃描,而且這兩個sql執行計劃中rows、bytes和cost值完全一緻。之是以是這樣,是因為這兒用的未開啟綁定變量窺探情況下的綁定變量,oracle不知道綁定變量值是什麼,隻能采用正常的計算cardinality方式,參考dbsnake的書,cbo用來估算cardinality的公式如下:

computed cardinality = original cardinality * selectivity

selectivity = 1 / num_distinct

收集統計資訊後,計算如下:

computed cardinality = 100001 * 1 / 2

約等于50001。是以無論是a還是b值,cbo認為結果集都是50001,占據一半的表記錄總量,自然會選擇全表掃描,而不是索引掃描。

下面我們說說綁定變量窺探,是9i引入的一個新特性,其作用就是會檢視sql謂詞的值,以便生成最佳的執行計劃,其受隐藏參數控制,預設為開啟。 

一個執行計劃異常變更引發的Oracle性能診斷優化

我們在綁定變量窺探開啟的情況下,再次執行上述兩條sql(差別僅是不用explain plan,使用dbms_xplan.display_cursor可以得到更詳細的資訊),首先a為條件的sql。

一個執行計劃異常變更引發的Oracle性能診斷優化

這次使用了全表掃描,窺探了綁定變量值是a。

再使用以b為條件的sql:

一個執行計劃異常變更引發的Oracle性能診斷優化

仍舊采用了全表掃描,綁定變量窺探值是a,因為隻有第一次硬解析的時候才會窺探綁定變量值,接下來執行都會使用第一次窺探的綁定變量值。b的記錄數隻有1條,1/100001的選擇率,顯然索引範圍掃描更合适。

為了讓sql重新窺探綁定變量值,我們重新整理共享池:

alter system flush shared_pool;

此時清空了所有之前儲存在共享池中的資訊,包括執行計劃,是以再次執行就會是硬解析,這次我們先使用b為條件。

一個執行計劃異常變更引發的Oracle性能診斷優化

可見窺探了綁定變量值是b,因為可以知道這個綁定變量:x的具體值,根據其值分布特點,選擇了索引範圍掃描。

再用a為查詢條件:

一個執行計劃異常變更引發的Oracle性能診斷優化

此時仍舊窺探綁定變量值為b,是以還會選擇索引範圍掃描,即使a值應該選擇全表掃描更高效。

總結:

綁定變量窺探會于第一次硬解析的時候,“窺探“綁定變量的值,進而根據該值的資訊,輔助選擇更加準确的執行計劃,就像上述示例中第一次執行a為條件的sql,知道a值占比重接近全表資料量,是以選擇了全表掃描。但若綁定變量列分布不均勻,則綁定變量窺探的副作用會很明顯,第二次以後的每次執行,無論綁定變量列值是什麼,都會僅使用第一次硬解析窺探的參數值,這就有可能選擇錯誤的執行計劃,就像上面這個實驗中說明的,第二次使用b為條件的sql,除非再次硬解析,否則這種情況不會改變。

簡而言之,資料分布不均勻的列使用綁定變量,尤其在11g之前,受綁定變量窺探的影響,可能會造成一些特殊值作為檢索條件選擇錯誤的執行計劃。11g的時候則推出了acs(自适應遊标),緩解了這個問題。

以上主要介紹了11g之前使用綁定變量和非綁定變量在解析效率方面的差別,以及綁定變量在綁定變量窺探開啟的情況下副作用的效果。雖然oltp系統,建議高并發的sql使用綁定變量,避免硬解析,可不是使用綁定變量就一定都好,尤其是11g之前,要充分了解綁定變量窺探副作用的原因,根據綁定變量列值真實分布情況,才能綜合判斷綁定變量的使用正确。

3、檢視綁定變量值的幾種方法

上一章我們了解了,綁定變量實際是一些占位符,可以讓僅查詢條件不同的sql語句可以重用解析樹和執行計劃,避免硬解析。綁定變量窺探則是第一次執行sql硬解析時,會窺探使用的綁定變量值,根據該值的分布特征,選擇更合适的執行計劃,副作用就是如果綁定變量列值分布不均勻,由于隻有第一次硬解析才會窺探,是以可能接下來的sql執行會選擇錯誤的執行計劃。

有時可能我們需要檢視某條sql使用了什麼綁定變量值,導緻執行計劃未用我們認為最佳的一種。以下就介紹一些常用的檢視綁定變量值的方法。

方法一:10046

使用level=4的10046事件,檢視生成的trace檔案。

一個執行計劃異常變更引發的Oracle性能診斷優化

可以看出綁定變量值是’z’。 

一個執行計劃異常變更引發的Oracle性能診斷優化

方法二:v$sql_bind_capture

首先找出sql對應的sql_id:

一個執行計劃異常變更引發的Oracle性能診斷優化

從v$sql_bind_capture可以看出兩個綁定變量占位符以及對應的值。

一個執行計劃異常變更引發的Oracle性能診斷優化

這裡有一點值得注意的就是,datatype_string列的描述是“綁定變量資料類型的文本表示”,開始我認為就是綁定變量字段的資料類型,但實際看來不是,datatype_string列隻是來告訴你綁定變量列是字元型,還是數值型。

一個執行計劃異常變更引發的Oracle性能診斷優化

我們此時換一下綁定變量值,發現v$sql_bind_capture資訊未變,dbsnake的書中曾說過當sql執行硬解析時綁定變量值被捕獲,并可從視圖v$sql_bind_capture中查詢。 

一個執行計劃異常變更引發的Oracle性能診斷優化

對于執行軟解析/軟軟解析的sql,預設情況下間隔15分鐘才能被捕獲,為了避免頻繁捕獲綁定變量值帶來的系統性能開銷,而且從常理上認為,既然使用了綁定變量,最佳方式就是值分布均勻,隻需要sql執行第一次硬解析時窺探一下,後續執行的sql執行計劃應該比較穩定,是以隻要能比較實時地檢視第一次綁定變量值即可。間隔15分鐘受隐藏參數_cursor_bind_capture_interval控制,預設值是900s,15分鐘。 

一個執行計劃異常變更引發的Oracle性能診斷優化

我們嘗試将捕獲綁定變量的間隔時間調短,該參數不支援session級别修改。

一個執行計劃異常變更引發的Oracle性能診斷優化

執行alter system級别操作。

一個執行計劃異常變更引發的Oracle性能診斷優化

等大約一分鐘,此時可以從v$sql_bind_capture查詢剛使用的綁定變量值。

一個執行計劃異常變更引發的Oracle性能診斷優化

方法三:awr資訊

(1) dba_hist_sqlbind視圖包含了v$sql_bind_capture的快照。

一個執行計劃異常變更引發的Oracle性能診斷優化

是以對應的sql語句,和v$sql_bind_capture很像。

select name,datatype_string,value_string,datatype from dba_hist_sqlbind where sql_id='...'

(2) 另一個視圖,dba_hist_sqlstat記錄了sql統計資訊的曆史資訊,他是基于一些标準,捕獲來自于v$sql的統計資訊。可以使用如下sql:

select

snap_id,

dbms_sqltune.extract_bind(bind_data,1).value_string bind1,

dbms_sqltune.extract_bind(bind_data,2).value_string bind2,

dbms_sqltune.extract_bind(bind_data,3).value_string bind3

from dba_hist_sqlstat

where sql_id = '...'

order by snap_id;

其中dbms_sqltune.extract_bind(bind_data,1).value_string取決于sql中綁定變量的數量。

第一次執行這兩條sql時,并未有任何結果傳回,我猜測可能是這條sql不符合awr采集的标準。從mos中查到這篇文章:《how to control the set of top sqls captured during awr snapshot generation (文檔 id 554831.1)》,用其中的方法修改下awr采集topnsql參數。

一個執行計劃異常變更引發的Oracle性能診斷優化

預設值是 

一個執行計劃異常變更引發的Oracle性能診斷優化

含義是

一個執行計劃異常變更引發的Oracle性能診斷優化

此時重新執行sql,預設awr會一小時采集一次,此時可以手工采集awr快照。

一個執行計劃異常變更引發的Oracle性能診斷優化

此時再次查詢dba_hist_sqlbind

一個執行計劃異常變更引發的Oracle性能診斷優化

再次查詢dba_hist_sqlstat

一個執行計劃異常變更引發的Oracle性能診斷優化

綁定變量值可以使用很多方法擷取,這裡隻是列舉了三種最常見的方法,我從網上看到有朋友還有用wrh$_sqlstat、v$sql等視圖查詢的例子,沒有深究,我覺得碰見問題時,可以快速使用一些常用的方法解決問題就可以了,當然時間充裕的話,建議還是多從原理層了解一些,做到觸類旁通則最好。

4、rolling invalidation

有一條sql,使用了綁定變量,檢視v$sqlarea發現version_count是2

一個執行計劃異常變更引發的Oracle性能診斷優化

檢視v$sql,發現有兩條記錄,分别對應了0和1兩個child cursor: 

一個執行計劃異常變更引發的Oracle性能診斷優化

再檢視這兩個child cursor對應的執行計劃: 

child cursor:0 

一個執行計劃異常變更引發的Oracle性能診斷優化

child cursor:1 

一個執行計劃異常變更引發的Oracle性能診斷優化

發現除了成本代價略有不同,其他通路路徑完全一緻。應用保證使用的相同使用者執行這條sql語句,綁定變量窺探關閉。問題就來了,為何同一條sql有兩個child cursor,且執行計劃一緻?

再抛一下,通過v$sql_shared_cursor視圖可以檢視遊标失效的原因,對比這兩個cursor,不同之一就是這個roll_invalid_mismatch字段的值,0号cursor值為n,1号cursor值為y。

一個執行計劃異常變更引發的Oracle性能診斷優化

另外,reason字段,0号cursor顯示了内容,1号cursor該字段值為空。

rolling invalidate window exceeded(3)

這個問題通過rolling cursor invalidations with dbms_stats.auto_invalidate (文檔 id 557661.1)這篇文章能夠很好地解釋。

大體意思是在10g之前,使用dbms_stats采集對象統計資訊,除非no_invalidate設為true,否則所有緩存在library cache中的遊标都會失效,下次執行時需要做硬解析。隐患就是對于一個oltp系統,會産生一次硬解析風暴,消耗大量的cpu、庫緩存以及共享池latch的争用,進而影響應用系統的響應時間。如果設定no_invalidate為false,則現有存儲的遊标不會使用更新的對象統計資訊,仍使用舊有執行計劃,直到下次硬解析,要麼因為時間太久,導緻cursor被刷出,要麼手工執行flush重新整理了共享池,這兩種情況下會重新執行硬解析,根據更新的對象統計資訊,生成更新的執行計劃。這麼做其實還是有可能出現硬解析風暴,特别是oltp系統,高并發時候,有sql語句頻繁通路。

使用dbms_stats.gather_xxx_stats的時候,有個參數no_invalidate:

一個執行計劃異常變更引發的Oracle性能診斷優化

預設是auto_invalidate,這表示是由oracle來決定什麼時候讓依賴的遊标失效。

10g之後,如果采集對象統計資訊使用的no_invalidate參數是auto_invalidate,則oracle會采用如下操作,來緩解可能的硬解析風暴。 

執行dbms_stats,所有依賴于這個已分析對象的緩存cursor遊标會被标記為rolling invalidation,并且記錄此時刻是t0。

下次某個session需要解析這個标記為rolling invalidation的cursor遊标時,會設定一個時間戳,其取值為_optimizer_invalidation_period定義的最大值範圍内的一個随機數。之是以是随機數,就是為了分散這些 invalidation的遊标,防止出現硬解析風暴。參數_optimizer_invalidation_period預設值是18000秒,5小時。記錄這次解析時間為t1,時間戳值為tmax。但此時,仍是重用了已有遊标,不會做硬解析,不會使用更新的統計資訊來生成一個新的執行計劃。

接下來這個遊标(标記了rolling invalidation和時間戳)的每次使用時,都會判斷目前時刻t2是否超過了時間戳tmax。如果未超過,則仍使用已存在的cursor。如果tmax已經超過了,則會讓此遊标失效,建立一個新的版本(一個新的child cursor子遊标),使用更新的執行計劃,并且新的子遊标會标記v$sql_shared_cursor中roll_invalid_mismatch的值。

這些和我上面碰見的情況基本一緻。

mos是附帶了一個實驗,可以根據實驗來體會下這種情況。 

1.為了容易觀察,設定_optimizer_invalidation_period為1分鐘。 

一個執行計劃異常變更引發的Oracle性能診斷優化

2.建立測試表,并采集統計資訊。

一個執行計劃異常變更引發的Oracle性能診斷優化

3.執行一次目标sql,并檢視v$sql_shared_cursor資訊。 

一個執行計劃異常變更引發的Oracle性能診斷優化

此時檢視這條sql的解析和執行次數都是1。 

一個執行計劃異常變更引發的Oracle性能診斷優化

4.再執行一次目标sql,select count(*) from x;,檢視這條sql的解析和執行次數是2。 

一個執行計劃異常變更引發的Oracle性能診斷優化

有人曾說過,11g中未必會按照_optimizer_invalidation_period參數定義的時間産生新的子遊标,我上面用的環境是11g,确實如此,等了2分鐘,執行目标sql,仍隻有一個子遊标。這樣的好處有人也說了,就是更加的随機,因為如果嚴格按照參數設定的時間失效,則有可能頻繁使用的遊标會在逾時後某一時刻集中做硬解析,還是會有資源的影響,隻是時間推遲了,是以如果是在逾時值基礎上又有随機分布,則可能會将硬解析的影響降到最低。

又等了一段時間,再查詢v$sql。 

一個執行計劃異常變更引發的Oracle性能診斷優化

确實産生了兩個子遊标,這裡需要注意first_load_time的時間是一樣的,因為他是parent父遊标的建立時間,顯然這兩個子遊标肯定是對應同一個父遊标,不同的就是last_load_time,這是子遊标的使用時間。

一個執行計劃異常變更引發的Oracle性能診斷優化

再看看v$sql_shared_cursor。 

一個執行計劃異常變更引發的Oracle性能診斷優化

兩個子遊标資訊,隻有一個r項值有差别,r是roll_invalid_mismatch,0号子遊标是n,1号子遊标是y,看看官方文檔對這個字段的說明。

一個執行計劃異常變更引發的Oracle性能診斷優化

表示的就是标記為rolling invalidation的遊标,已經是超過了時間視窗,此時0号子遊标已經過期,1号子遊标使用最新的統計資訊,來生成最新的執行計劃。

這就解釋了為何同一條sql,執行計劃一緻,但卻有兩個子遊标的情況。

mos中還描述了一些遊标使用的場景: 

如果一個遊标被标記為rolling invalidation,但是再不會做解析,則這個遊标不會失效,最終還是可能根據lru被刷出共享池。

如果一個遊标被标記為rolling invalidation,後面隻會解析一次,那麼這個遊标依然不會失效(僅僅使用時間戳标記),最終還是可能根據lru被刷出共享池。

頻繁使用的遊标,在超過時間戳tmax值後,下次解析時就會被置為失效。 

很明顯,上面的這些方法是有效的,因為失效标記僅僅适用于這些頻繁重用的遊标,對于其他場景的遊标可以忽略,未有影響。

5、聚簇因子(clustering factor)

聚簇因子,clustering factor,聽着名字就很高大上,很學術。題外話,記得幾年前的一次内部分享,dbsnake介紹一案例的時候,曾問過在場同僚其中涉及的一個知識點是什麼,如果知道就意味着你對索引的了解很深入,可惜當時沒人反應,作為小白的我自然也不知道,當時的這個知識點就是聚簇因子,下來我仔細了解了下,确實這些東東,如果經常用到自然脫口而出,可惜這種機會隻能靠自己。

我們先看下官方對cf介紹。

一個執行計劃異常變更引發的Oracle性能診斷優化

索引聚簇因子衡量的是索引字段存儲順序和表中資料存儲順序的符合程度。兩者存儲順序越接近,聚簇因子值就越小。

聚簇因子的用處在于可以粗略估算根據索引回表需要的io數量。

如果cf值高,oracle執行一個相對較大的索引範圍掃描時就會需要相對多的io數量。這些索引項指向的是随機的表塊,資料庫為了根據索引檢索表中資料,不得不一次又一次地讀取相同的資料塊。

如果cf值低,oracle執行一個相對較大的索引範圍掃描時就會需要相對少的io數量。這些索引鍵值可能指向相同的資料塊,資料庫不需要重複讀取同一個資料塊。

文中還舉了一個例子,如下表employees中資料是按照last name的字母順序存儲的。

一個執行計劃異常變更引發的Oracle性能診斷優化

如果last name是索引字段,可以看出索引的存儲順序(blockxrowy可以抽象地看作rowid),即連續的幾個索引鍵值指向的是同一個資料塊。

一個執行計劃異常變更引發的Oracle性能診斷優化

如果此時id是索引字段,可以看出連續的幾個索引鍵值對應的可能是不同的資料塊,而且有可能幾個順序間隔不多的鍵值指向的是同一個資料塊,如果這是一個龐大的索引和表,buffer cache再小一些,使用id字段作為檢索條件的sql并發再高一些,很可能之前剛從資料檔案中加載至buffer cache,馬上就會根據lru算法age out,但一會又再次加載至buffer cache,反反複複,各種latch等的資源争用就會累積起來,進而可能對系統性能造成影響。 

一個執行計劃異常變更引發的Oracle性能診斷優化

dba/all/user_indexes視圖有一列clustering_factor,表明該索引的聚簇因子值。

一個執行計劃異常變更引發的Oracle性能診斷優化

摘自dbsnake書中對于cf值計算算法的叙述:

cf初始值是1。

oracle首先定為至目标索引最左邊的葉子塊。

從最左邊的葉子塊的第一個索引鍵值所在的索引行開始順序掃描,oracle比較目前索引行的roid和他之前相鄰的索引行的rowid,若這兩rowid并不是指向同一個表塊,則将聚簇因子值遞增1,如果指向同一個rowid,則不改變目前聚簇因子值。比對rowid的時候并不需要回表通路相應的表塊。(注:原因就是根據rowid的值是可以計算出block資訊) 

直到順序掃描完目标索引所有葉子塊的所有索引行。 

掃描操作完成後,聚簇因子目前值就是會被存儲在資料字典中,就是上面視圖中clustering factor列。

說了這麼多,cf有什麼實際意義?個人了解,cbo模式的優化器會綜合考慮各種因素來判斷一條sql不同執行計劃對應的成本值,選擇成本值最低的一個執行計劃,cf實際影響的是根據索引回表需要的io數量,自然也在其考慮的範圍之内,是以cf值的高低有時會影響cbo對不同執行計劃的選擇。

實驗: 

1.建立測試表

測試表有兩列number類型的字段,其中id1是按照順序存儲,id2是無序存儲,id1和id2各有一個非唯一索引。

一個執行計劃異常變更引發的Oracle性能診斷優化

2.采集統計資訊

一個執行計劃異常變更引發的Oracle性能診斷優化

dba/all/user_indexes中有一注釋:

“column names followed by an asterisk are populated only if you collect statistics on the index using the dbms_stats package.“

即使用dbms_stats包收集索引統計資訊的時候,clustering_factor才會有值。

從dba_indexes中可以看出id1對應的索引cf隻有204,id2對應的索引cf有99481,表的資料量是100000,就是說這個id2中所有葉子塊的索引行排列順序幾乎和表中資料存儲的順序完全不一緻。

一個執行計劃異常變更引發的Oracle性能診斷優化

3.cf對執行計劃選擇的影響

使用id1 between 1 and 1000作為檢索條件,可以看出使用了id1索引範圍掃描。

一個執行計劃異常變更引發的Oracle性能診斷優化

使用id2 between 1 and 1000作為檢索條件,這次卻選擇了全表掃描,沒有選擇id2索引掃描。

一個執行計劃異常變更引發的Oracle性能診斷優化

如果我們強制使用id2索引,無論從cost,還是consistent gets,都要高于全表掃描。

一個執行計劃異常變更引發的Oracle性能診斷優化

究其原因,還可以參考dbsnake書中對于索引範圍掃描的算法。

irs cost = i/o cost + cpu cost

i/o cost = index access cost + table access i/o cost

index access cost = blevel + ceil(#leaf_blocks * ix_sel)

table access i/o cost = ceil(clustering_factor * ix_sel_with_filters)

我們可以檢索視圖發現,id1和id2的索引leaf_blocks等列值均相等,隻有clustering_factor不同,進而可以粗略認為索引範圍掃描的成本和聚簇因子的大小成正比。

進而我們可以這麼嘗試,人為将id2的索引聚簇因子值改為200。

一個執行計劃異常變更引發的Oracle性能診斷優化

可以看出此時選擇了id2的索引範圍掃描。

一個執行計劃異常變更引發的Oracle性能診斷優化

但相應consistent gets值依舊很大,我猜原因就是計算執行計劃成本值,cbo會根據相關統計資訊值來計算,我們人為設定了索引的聚簇因子為一個很小的值,計算出來的成本值小于全表掃描,是以選擇了使用索引的執行計劃,但實際回表等操作需要消耗的資源其實并沒有少。

一個執行計劃異常變更引發的Oracle性能診斷優化

如果要消除聚簇因子的影響,隻能對表中資料按照目标索引鍵值的順序重新存儲,例如,create table t1_cf_0 as select * from t1_cf order by id2;

但這麼做帶來的問題就是,可能id2的聚簇因子下降了,相對id1的聚簇因子上升了,有些顧此失彼的意思。是以根據實際業務需求,選擇正确的表資料組織形式,或者隻能通過其他優化方式,來減小聚簇因子的影響。

之前曾發過一個如何讓cf值小的讨論帖,有興趣的朋友可以參考, 

<a href="http://www.itpub.net/thread-1910003-1-1.html">http://www.itpub.net/thread-1910003-1-1.html</a>

總結: 

聚簇因子表示索引鍵值的排列順序和表中資料排列順序的相似程度。

可以粗略認為索引範圍掃描的成本,和聚簇因子的大小成正比,從索引範圍掃描的計算方法可以推出這個結論。

是否需要重新組織表中資料存儲順序,以降低某一個索引的聚簇因子值,需要結合實際需求來判斷,因為若表中存在多個索引,很可能造成顧此失彼的情況。原文釋出時間為:2017-05-12

本文來自雲栖社群合作夥伴dbaplus