1. 程式人生 > >一個執行計劃異常變更引發的Oracle效能診斷優化

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

作者介紹

bisal,Oracle技術愛好者。利用業餘時間學習並通過了SCJP1.4、Oracle 10g/11g OCP、Oracle 11g OCM認證,國內首批加入Oracle YEP的成員。訂閱號:bisal的個人雜貨鋪。

最近有一個OLTP應用使用的Oracle資料庫突然出現效能問題,DBA發現有一些delete語句執行時間驟長,消耗大量系統資源,導致應用響應時間變長積Q。

輔助資訊:

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

2.據開發人員反饋,從之前的應用日誌看,未出現處理時間逐步變長的現象。

3.這是一套RAC+DG的環境,11g的版本。

4.這次突然出現大量執行時間超長的SQL語句,是一條刪除語句,deletefromtablewherekey1=:1andkey2=:2and…(省略此案例不會用到的其他條件),應用正常的處理邏輯中都會使用這條語句,因此併發較高,使用了繫結變數,key1和key2欄位不是主鍵,但有索引,存在直方圖。

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

本文目錄:

基礎知識介紹:

  1. 可能造成SQL執行計劃發生改變的一個示例
  2. 繫結變數窺探
  3. 檢視繫結變數值的幾種方法
  4. rolling invalidation
  5. 聚簇因子(Clustering Factor)
  6. 查詢執行計劃的幾種方法
  7. AWR
  8. ASH
  9. SQL AWR
  10. 直方圖
  11. SQL Profile

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

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

實驗: 建立測試表t1,其中name欄位設定索引,取值為10000個A和1個B。

SQL

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

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

掃描

顯而易見,因為取值為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,全表掃描的成本更低一些。

SQL

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

索引掃描

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

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

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

2、繫結變數窺探

首先什麼是繫結變數?
一條SQL語句在解析階段,會根據SQL文字對應的雜湊值在庫快取中查詢是否有匹配的Parent Cursor,進而找出是否有可重用的解析樹和執行計劃,若沒有則要重新生成一遍,OLTP系統中,高併發的SQL若每次均需要重複執行這些操作,即所謂的硬解析,消耗會比較大,進而影響系統性能,所以就需要使用繫結變數。繫結變數其實就是一些佔位符,用於替換SQL文字中具體輸入值,例如以下兩條SQL:

在Oracle看來,是兩條完全不同的SQL,即對應SQL文字雜湊值不同,因為where條件中一個id是1,一個是2,1和2的ASCII是不同的,可實際上這兩條SQL除了查詢條件不同,其他的文字字元均一致,儘管如此,這種情況下,Oracle還是會重複執行解析的操作,生成各自的遊標。

兩條記錄,說明Oracle認為這兩條SQL是不同。

如果使用繫結變數:

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

引數值

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

如下是測試表:

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

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

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

首先A為條件。

顯示使用了全表掃描。

再以B為條件。

全表掃描

發現仍舊是全表掃描,我們之前知道B值記錄只有一條,應該使用索引範圍掃描,而且這兩個SQL執行計劃中Rows、Bytes和Cost值完全一致。之所以是這樣,是因為這兒用的未開啟繫結變數窺探情況下的繫結變數,Oracle不知道繫結變數值是什麼,只能採用常規的計算Cardinality方式,參考dbsnake的書,CBO用來估算Cardinality的公式如下:

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

約等於50001。因此無論是A還是B值,CBO認為結果集都是50001,佔據一半的表記錄總量,自然會選擇全表掃描,而不是索引掃描。

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

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

這次使用了全表掃描,窺探了繫結變數值是A。

再使用以B為條件的SQL:

仍舊採用了全表掃描,繫結變數窺探值是A,因為只有第一次硬解析的時候才會窺探繫結變數值,接下來執行都會使用第一次窺探的繫結變數值。B的記錄數只有1條,1/100001的選擇率,顯然索引範圍掃描更合適。

為了讓SQL重新窺探繫結變數值,我們重新整理共享池:

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

可見窺探了繫結變數值是B,因為可以知道這個繫結變數:x的具體值,根據其值分佈特點,選擇了索引範圍掃描。

再用A為查詢條件:

此時仍舊窺探繫結變數值為B,因此還會選擇索引範圍掃描,即使A值應該選擇全表掃描更高效。

總結:

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

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

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

3、檢視繫結變數值的幾種方法

上一章我們瞭解了,繫結變數實際是一些佔位符,可以讓僅查詢條件不同的SQL語句可以重用解析樹和執行計劃,避免硬解析。繫結變數窺探則是第一次執行SQL硬解析時,會窺探使用的繫結變數值,根據該值的分佈特徵,選擇更合適的執行計劃,副作用就是如果繫結變數列值分佈不均勻,由於只有第一次硬解析才會窺探,所以可能接下來的SQL執行會選擇錯誤的執行計劃。

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

方法一:10046

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

可以看出繫結變數值是’Z’。

方法二:v$sql_bind_capture

首先找出SQL對應的sql_id:

從v$sql_bind_capture可以看出兩個繫結變數佔位符以及對應的值。

這裡有一點值得注意的就是,DATATYPE_STRING列的描述是“繫結變數資料型別的文字表示”,開始我認為就是繫結變數欄位的資料型別,但實際看來不是,DATATYPE_STRING列只是來告訴你繫結變數列是字元型,還是數值型。

我們此時換一下繫結變數值,發現v$sql_bind_capture資訊未變,dbsnake的書中曾說過當SQL執行硬解析時繫結變數值被捕獲,並可從檢視v$sql_bind_capture中查詢。

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

我們嘗試將捕獲繫結變數的間隔時間調短,該引數不支援session級別修改。

執行alter system級別操作。

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

方法三:AWR資訊

(1)DBA_HIST_SQLBIND檢視包含了v$sql_bind_capture的快照。

因此對應的SQL語句,和v$sql_bind_capture很像。

(2)另一個檢視,DBA_HIST_SQLSTAT記錄了SQL統計資訊的歷史資訊,他是基於一些標準,捕獲來自於V$SQL的統計資訊。可以使用如下SQL:

其中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引數。

預設值是

含義是

此時重新執行SQL,預設AWR會一小時採集一次,此時可以手工採集AWR快照。

此時再次查詢DBA_HIST_SQLBIND

再次查詢DBA_HIST_SQLSTAT

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

4、rolling invalidation

有一條SQL,使用了繫結變數,檢視V$SQLAREA發現version_count是2

檢視V$SQL,發現有兩條記錄,分別對應了0和1兩個child cursor:

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

child cursor:0

hild cursor:1

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

再拋一下,通過V$SQL_SHARED_CURSOR檢視可以檢視遊標失效的原因,對比這兩個cursor,不同之一就是這個ROLL_INVALID_MISMATCH欄位的值,0號cursor值為N,1號cursor值為Y。

另外,REASON欄位,0號cursor顯示了內容,1號cursor該欄位值為空。

這個問題通過Rolling Cursor Invalidations with DBMS_STATS.AUTO_INVALIDATE (文件 ID 557661.1)這篇文章能夠很好地解釋。

這個問題通過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:

預設是AUTO_INVALIDATE,這表示是由Oracle來決定什麼時候讓依賴的遊標失效。

10g之後,如果採集物件統計資訊使用的no_invalidate引數是auto_invalidate,則Oracle會採用如下操作,來緩解可能的硬解析風暴。

  1. 執行dbms_stats,所有依賴於這個已分析物件的快取cursor遊標會被標記為rolling invalidation,並且記錄此時刻是T0。
  2. 下次某個session需要解析這個標記為rolling invalidation的cursor遊標時,會設定一個時間戳,其取值為_optimizer_invalidation_period定義的最大值範圍內的一個隨機數。之所以是隨機數,就是為了分散這些 invalidation的遊標,防止出現硬解析風暴。引數_optimizer_invalidation_period預設值是18000秒,5小時。記錄這次解析時間為T1,時間戳值為Tmax。但此時,仍是重用了已有遊標,不會做硬解析,不會使用更新的統計資訊來生成一個新的執行計劃。
  3. 接下來這個遊標(標記了rolling invalidation和時間戳)的每次使用時,都會判斷當前時刻T2是否超過了時間戳Tmax。如果未超過,則仍使用已存在的cursor。如果Tmax已經超過了,則會讓此遊標失效,建立一個新的版本(一個新的child cursor子游標),使用更新的執行計劃,並且新的子游標會標記V$SQL_SHARED_CURSOR中ROLL_INVALID_MISMATCH的值。

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

MOS是附帶了一個實驗,可以根據實驗來體會下這種情況。
1.為了容易觀察,設定_optimizer_invalidation_period為1分鐘。

2.建立測試表,並採集統計資訊。

3.執行一次目標SQL,並檢視V$SQL_SHARED_CURSOR資訊。

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

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

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

又等了一段時間,再查詢V$SQL。

確實產生了兩個子游標,這裡需要注意FIRST_LOAD_TIME的時間是一樣的,因為他是parent父遊標的建立時間,顯然這兩個子游標肯定是對應同一個父遊標,不同的就是LAST_LOAD_TIME,這是子游標的使用時間。

再看看V$SQL_SHARED_CURSOR。

兩個子游標資訊,只有一個R項值有差別,R是ROLL_INVALID_MISMATCH,0號子游標是N,1號子游標是Y,看看官方文件對這個欄位的說明。

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

這就解釋了為何同一條SQL,執行計劃一致,但卻有兩個子游標的情況。

MOS中還描述了一些遊標使用的場景:

  1. 如果一個遊標被標記為rolling invalidation,但是再不會做解析,則這個遊標不會失效,最終還是可能根據LRU被刷出共享池。
  2. 如果一個遊標被標記為rolling invalidation,後面只會解析一次,那麼這個遊標依然不會失效(僅僅使用時間戳標記),最終還是可能根據LRU被刷出共享池。
  3. 頻繁使用的遊標,在超過時間戳Tmax值後,下次解析時就會被置為失效。

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

5、聚簇因子(Clustering Factor)

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

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

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

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

  • 如果CF值高,Oracle執行一個相對較大的索引範圍掃描時就會需要相對多的IO數量。這些索引項指向的是隨機的表塊,資料庫為了根據索引檢索表中資料,不得不一次又一次地讀取相同的資料塊。
  • 如果CF值低,Oracle執行一個相對較大的索引範圍掃描時就會需要相對少的IO數量。這些索引鍵值可能指向相同的資料塊,資料庫不需要重複讀取同一個資料塊。

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

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

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

DBA/ALL/USER_INDEXES檢視有一列CLUSTERING_FACTOR,表明該索引的聚簇因子值。

摘自dbsnake書中對於CF值計算演算法的敘述:

1.CF初始值是1。

2.Oracle首先定為至目標索引最左邊的葉子塊。

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

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

4.掃描操作完成後,聚簇因子當前值就是會被儲存在資料字典中,就是上面檢視中CLUSTERINGFACTOR列。

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

實驗:

1.建立測試表

測試表有兩列NUMBER型別的欄位,其中id1是按照順序儲存,id2是無序儲存,id1和id2各有一個非唯一索引。

2.採集統計資訊

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中所有葉子塊的索引行排列順序幾乎和表中資料儲存的順序完全不一致。

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

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

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

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

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

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

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

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

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

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

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

總結:

  • 聚簇因子表示索引鍵值的排列順序和表中資料排列順序的相似程度。
  • 可以粗略認為索引範圍掃描的成本,和聚簇因子的大小成正比,從索引範圍掃描的計算方法可以推出這個結論。
  • 是否需要重新組織表中資料儲存順序,以降低某一個索引的聚簇因子值,需要結合實際需求來判斷,因為若表中存在多個索引,很可能造成顧此失彼的情況。

文章來自微信公眾號:DBAplus社群