天天看點

一個執行計劃異常變更的案例 - 外傳之綁定變量窺探

上一篇文章是前傳,簡單介紹了這個案例的一些背景,從這篇文章開始,會有幾篇外傳,主要介紹處理這個案例過程中涉及的知識點,最後是一篇正傳,針對這案例的真實原因的剖析和解決。

第一篇外傳就是綁定變量窺探。

首先什麼是綁定變量?

一條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的公式如下:

Computed Cardinality = Original Cardinality * Selectivity Selectivity = 1 / NUM_DISTINCT

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

Computed Cardinality = 100001 * 1 / 2

約等于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之前,要充分了解綁定變量窺探副作用的原因,根據綁定變量列值真實分布情況,才能綜合判斷綁定變量的使用正确。