Shared pool深入分析及效能調整
阿新 • • 發佈:2019-01-10
shared pool的內部管理機制 3.1解析SQL語句的過程
為了將使用者寫的可讀的SQL文字轉化為oracle認識的且可執行的語句,這個過程就叫做解析過程。
解析分為硬解析和軟解析。當一句SQL第一次被執行時必須進行硬解析。
當客戶端發出一條SQL語句(也可以是一個儲存過程或者一個匿名PL/SQL塊)進入shared pool時
(注意,我們從前面已經知道,oracle對這些SQL不叫做SQL語句,而是稱為遊標(cursor)。因為oracle在處理SQL時,需要很多相關的輔助資訊,這些輔助資訊與SQL語句一起組成了遊標),oracle首先將SQL文字轉化為ASCII字元,然後根據hash函式計算其對應的hash值(hash_value)。根據計算出的hash值到library cache中找到對應的bucket,然後比較bucket裡是否存在該SQL語句。
如果不存在,則需要按照我們前面所描述的,獲得shared pool latch,然後在shared pool中的可用chunk連結串列(也就是bucket)上找到一個可用的chunk,然後釋放shared pool latch。在獲得了chunk以後,這塊chunk就可以認為是進入了library cache。然後,進行硬解析過程。硬解析包括以下幾個步驟:
1) 對SQL語句進行語法檢查,看是否有語法錯誤。比如沒有寫from等。如果有,則退出解析過程。
2) 到資料字典裡校驗SQL語句涉及的物件和列是否都存在。如果不存在,則退出解析過程。
3) 將物件進行名稱轉換。比如將同名詞翻譯成實際的物件等。如果轉換失敗,則退出解析過程。
4) 檢查遊標裡使用者是否具有訪問SQL語句裡所引用的物件的許可權。如果沒有許可權,則退出解析過程。
5) 通過優化器建立一個最優的執行計劃。這一步是最消耗CPU資源的。
6) 將該遊標所產生的執行計劃、SQL文字等裝載進library cache的若干個heap中。
在硬解析的過程中,程序會一直持有library cach latch,直到硬解析結束。硬解析結束以後,會為該SQL產生兩個遊標,一個是父遊標,另一個是子游標。父遊標裡主要包含兩種資訊:SQL文字以及優化目標(optimizer goal)。父遊標在第一次開啟時被鎖定,直到其他所有的session都關閉該遊標後才被解鎖。當父遊標被鎖定的時候是不能被交換出library cache的,只有在解鎖以後才能被交換出library cache,這時該父遊標對應的所有子游標也被交換出library cache。子游標包括遊標所有的資訊,比如具體的執行計劃、繫結變數等。前面圖四中看到的CHILDREN部分就是子游標所對應的handle的資訊。子游標隨時可以被交換出library cache,當子游標被交換出library cache時,oracle可以利用父遊標的資訊重新構建出一個子遊標來,這個過程叫reload。可以使用下面的方式來確定reload的比率:
SELECT 100*sum(reloads)/sum(pins) Reload_Ratio FROM v$librarycache;
一個父遊標可以對應多個子遊標。子游標具體的個數可以從v$sqlarea的version_count欄位體現出來。而每個具體的子游標則全都在v$sql裡體現。當具體的繫結變數的值與上次的繫結變數的值有較大差異(比如上次執行的繫結變數的值的長度是6位,而這次執行的繫結變數的值的長度是200位)時或者當SQL語句完全相同,但是所引用的物件屬於不同的schema時,都會建立一個新的子游標。
如果在bucket中找到了該SQL語句,則說明該SQL語句以前執行過,於是進行軟解析。軟解析是相對於硬解析而言的,如果解析過程中,可以從硬解析的步驟中去掉一個或多個的話,這樣的解析就是軟解析。軟解析分為以下三種類型。
1) 第一種是某個session發出的SQL語句與library cache裡其他session發出的SQL語句一致。這時,該解析過程中可以去掉硬解析中的5和6這兩步,但是仍然要進行硬解析過程中的2、3、4步驟:也就是表名和列名檢查、名稱轉換和許可權檢查。
2) 第二種是某個session發出的SQL語句與library cache裡該同一個session之前發出的SQL語句一致。這時,該解析過程中可以去掉硬解析中的2、3、5和6這四步,但是仍然要進行許可權檢查,因為可能通過grant改變了該session使用者的許可權。
3) 第三種是當設定了初始化引數session_cached_cursors時,當某個session對相同的cursor進行第三次訪問時,將在該session的PGA裡建立一個標記,並且該遊標即使已經被關閉也不會從library cache中交換出去。這樣,該session以後再執行相同的SQL語句時,將跳過硬解析的所有步驟。這種情況下,是最高效的解析方式,但是會消耗很大的記憶體。
我們先來舉一個例子說明如果在解析過程中發生語法或語義錯誤時,在shared pool中是怎樣體現的。
SQL> select object_type fromm sharedpool_test111;
ORA-00942: 表或檢視不存在
然後我們以level 16轉儲library cache,並開啟轉儲檔案,找到相應的部分,如下圖八所示。可以看到,
該SQL語句在語法上是錯誤的(from寫成了fromm),oracle仍然在shared pool中為其分配了一個chunk,然後該chunk進入library cache,並在library cache中分配了一個bucket,同時也生成了heap 0,但是該heap 0中不存在相應的一些如dependency table等table的部分,以及data block的部分。我看到有些資料上說SQL語句是先進行語法分析,如果通過語法分析以後,則應用hash函式生成hash值,然後再去shared pool中分配chunk。實際上從這個例項已經可以看出,這個說法是錯誤的。oracle始終都是先對SQL生成hash值(不論該SQL語法上是否正確),再根據hash值到對應的可用chunk連結串列(也就是bucket)裡分配chunk,然後進入語法解析等解析過程。
圖八
我們再舉一個例子來說明解析正確的SQL語句的過程。如下所示。
SQL> alter system flush shared_pool;
SQL> variable v_obj_id number;
SQL> exec :v_obj_id := 4474;
SQL> select object_id,object_name from sharedpool_test where object_id=:v_obj_id;
OBJECT_ID OBJECT_NAME
---------- ---------------------------
4474 AGGXMLIMP
SQL> variable v_obj_id varchar2(10);
SQL> exec :v_obj_id := '4474';
SQL> select object_id,object_name from sharedpool_test where object_id=:v_obj_id;
OBJECT_ID OBJECT_NAME
---------- ---------------------------
4474 AGGXMLIMP
然後,我們以level 16來轉儲library cache。可以看到如下圖九所示的內容。很明顯的看到,子游標的
部分包含兩條記錄,這也就說明該SQL語句產生了兩個子游標。雖然我們從SQL文字上看,前後兩次執行的SQL語句是一樣的。只有繫結變數的型別發生了改變,第一次是number型,而第二次是varchar2型。可正是這資料型別的變化導致了該SQL語句的執行計劃不能得到共享,從而產生了兩個子游標。這時,我們根據子游標的兩個handle:6757f358和674440fc找到對應的heap 0的話,就可以看到這兩個heap 0中所記錄的heap 6是兩個完全不同的記憶體塊,這也說明前後兩次執行SQL並沒有真正得到共享。
圖九
我們還可以根據該SQL的hash值(f390fb6f)來看看動態效能視圖裡是如何表現的。
SQL> select to_number('f390fb6f','xxxxxxxx') from dual;
TO_NUMBER('F390FB6F','XXXXXXXX
------------------------------
4086365039
SQL> select sql_text,version_count from v$sqlarea where hash_value=4086365039;
SQL_TEXT VERSION_COUNT
------------------------------------------------------------------------- ------------
select object_id,object_name from sharedpool_test where object_id=:v_obj_id 2
SQL> select sql_text,child_address,address from v$sql where hash_value=4086365039;
SQL_TEXT CHILD_ADDRESS ADDRESS
-------------------------------------------------------------------- ----------- --------
select object_id,object_name from sharedpool_test where object_id=:v_obj_id 6757F358 676B6D08
select object_id,object_name from sharedpool_test where object_id=:v_obj_id 674440FC 676B6D08
從記錄父遊標的檢視v$sqlarea的version_count列可以看到,該SQL語句有2個子遊標。而從記錄子游標的檢視v$sql裡可以看到,該SQL文字確實有兩條記錄,而且它們的SQL文字所處的地址(address列)也是一樣的,但是子地址(child_address)卻不一樣。這裡的子地址實際就是子游標所對應的heap 0的控制代碼。
由此我們也可以看到,存在許多因素可能導致SQL語句不能共享。常見的因素包括, SQL文字大小寫不一致、SQL語句的繫結變數的型別不一致、SQL語句涉及到的物件名稱雖然一致但是位於不同的schema下、SQL的優化器模式不一致(比如新增提示、修改了optimizer_mode引數等)等。
為了將使用者寫的可讀的SQL文字轉化為oracle認識的且可執行的語句,這個過程就叫做解析過程。
解析分為硬解析和軟解析。當一句SQL第一次被執行時必須進行硬解析。
當客戶端發出一條SQL語句(也可以是一個儲存過程或者一個匿名PL/SQL塊)進入shared pool時
(注意,我們從前面已經知道,oracle對這些SQL不叫做SQL語句,而是稱為遊標(cursor)。因為oracle在處理SQL時,需要很多相關的輔助資訊,這些輔助資訊與SQL語句一起組成了遊標),oracle首先將SQL文字轉化為ASCII字元,然後根據hash函式計算其對應的hash值(hash_value)。根據計算出的hash值到library cache中找到對應的bucket,然後比較bucket裡是否存在該SQL語句。
如果不存在,則需要按照我們前面所描述的,獲得shared pool latch,然後在shared pool中的可用chunk連結串列(也就是bucket)上找到一個可用的chunk,然後釋放shared pool latch。在獲得了chunk以後,這塊chunk就可以認為是進入了library cache。然後,進行硬解析過程。硬解析包括以下幾個步驟:
1) 對SQL語句進行語法檢查,看是否有語法錯誤。比如沒有寫from等。如果有,則退出解析過程。
2) 到資料字典裡校驗SQL語句涉及的物件和列是否都存在。如果不存在,則退出解析過程。
3) 將物件進行名稱轉換。比如將同名詞翻譯成實際的物件等。如果轉換失敗,則退出解析過程。
4) 檢查遊標裡使用者是否具有訪問SQL語句裡所引用的物件的許可權。如果沒有許可權,則退出解析過程。
5) 通過優化器建立一個最優的執行計劃。這一步是最消耗CPU資源的。
6) 將該遊標所產生的執行計劃、SQL文字等裝載進library cache的若干個heap中。
在硬解析的過程中,程序會一直持有library cach latch,直到硬解析結束。硬解析結束以後,會為該SQL產生兩個遊標,一個是父遊標,另一個是子游標。父遊標裡主要包含兩種資訊:SQL文字以及優化目標(optimizer goal)。父遊標在第一次開啟時被鎖定,直到其他所有的session都關閉該遊標後才被解鎖。當父遊標被鎖定的時候是不能被交換出library cache的,只有在解鎖以後才能被交換出library cache,這時該父遊標對應的所有子游標也被交換出library cache。子游標包括遊標所有的資訊,比如具體的執行計劃、繫結變數等。前面圖四中看到的CHILDREN部分就是子游標所對應的handle的資訊。子游標隨時可以被交換出library cache,當子游標被交換出library cache時,oracle可以利用父遊標的資訊重新構建出一個子遊標來,這個過程叫reload。可以使用下面的方式來確定reload的比率:
SELECT 100*sum(reloads)/sum(pins) Reload_Ratio FROM v$librarycache;
一個父遊標可以對應多個子遊標。子游標具體的個數可以從v$sqlarea的version_count欄位體現出來。而每個具體的子游標則全都在v$sql裡體現。當具體的繫結變數的值與上次的繫結變數的值有較大差異(比如上次執行的繫結變數的值的長度是6位,而這次執行的繫結變數的值的長度是200位)時或者當SQL語句完全相同,但是所引用的物件屬於不同的schema時,都會建立一個新的子游標。
如果在bucket中找到了該SQL語句,則說明該SQL語句以前執行過,於是進行軟解析。軟解析是相對於硬解析而言的,如果解析過程中,可以從硬解析的步驟中去掉一個或多個的話,這樣的解析就是軟解析。軟解析分為以下三種類型。
1) 第一種是某個session發出的SQL語句與library cache裡其他session發出的SQL語句一致。這時,該解析過程中可以去掉硬解析中的5和6這兩步,但是仍然要進行硬解析過程中的2、3、4步驟:也就是表名和列名檢查、名稱轉換和許可權檢查。
2) 第二種是某個session發出的SQL語句與library cache裡該同一個session之前發出的SQL語句一致。這時,該解析過程中可以去掉硬解析中的2、3、5和6這四步,但是仍然要進行許可權檢查,因為可能通過grant改變了該session使用者的許可權。
3) 第三種是當設定了初始化引數session_cached_cursors時,當某個session對相同的cursor進行第三次訪問時,將在該session的PGA裡建立一個標記,並且該遊標即使已經被關閉也不會從library cache中交換出去。這樣,該session以後再執行相同的SQL語句時,將跳過硬解析的所有步驟。這種情況下,是最高效的解析方式,但是會消耗很大的記憶體。
我們先來舉一個例子說明如果在解析過程中發生語法或語義錯誤時,在shared pool中是怎樣體現的。
SQL> select object_type fromm sharedpool_test111;
ORA-00942: 表或檢視不存在
然後我們以level 16轉儲library cache,並開啟轉儲檔案,找到相應的部分,如下圖八所示。可以看到,
該SQL語句在語法上是錯誤的(from寫成了fromm),oracle仍然在shared pool中為其分配了一個chunk,然後該chunk進入library cache,並在library cache中分配了一個bucket,同時也生成了heap 0,但是該heap 0中不存在相應的一些如dependency table等table的部分,以及data block的部分。我看到有些資料上說SQL語句是先進行語法分析,如果通過語法分析以後,則應用hash函式生成hash值,然後再去shared pool中分配chunk。實際上從這個例項已經可以看出,這個說法是錯誤的。oracle始終都是先對SQL生成hash值(不論該SQL語法上是否正確),再根據hash值到對應的可用chunk連結串列(也就是bucket)裡分配chunk,然後進入語法解析等解析過程。
圖八
我們再舉一個例子來說明解析正確的SQL語句的過程。如下所示。
SQL> alter system flush shared_pool;
SQL> variable v_obj_id number;
SQL> exec :v_obj_id := 4474;
SQL> select object_id,object_name from sharedpool_test where object_id=:v_obj_id;
OBJECT_ID OBJECT_NAME
---------- ---------------------------
4474 AGGXMLIMP
SQL> variable v_obj_id varchar2(10);
SQL> exec :v_obj_id := '4474';
SQL> select object_id,object_name from sharedpool_test where object_id=:v_obj_id;
OBJECT_ID OBJECT_NAME
---------- ---------------------------
4474 AGGXMLIMP
然後,我們以level 16來轉儲library cache。可以看到如下圖九所示的內容。很明顯的看到,子游標的
部分包含兩條記錄,這也就說明該SQL語句產生了兩個子游標。雖然我們從SQL文字上看,前後兩次執行的SQL語句是一樣的。只有繫結變數的型別發生了改變,第一次是number型,而第二次是varchar2型。可正是這資料型別的變化導致了該SQL語句的執行計劃不能得到共享,從而產生了兩個子游標。這時,我們根據子游標的兩個handle:6757f358和674440fc找到對應的heap 0的話,就可以看到這兩個heap 0中所記錄的heap 6是兩個完全不同的記憶體塊,這也說明前後兩次執行SQL並沒有真正得到共享。
圖九
我們還可以根據該SQL的hash值(f390fb6f)來看看動態效能視圖裡是如何表現的。
SQL> select to_number('f390fb6f','xxxxxxxx') from dual;
TO_NUMBER('F390FB6F','XXXXXXXX
------------------------------
4086365039
SQL> select sql_text,version_count from v$sqlarea where hash_value=4086365039;
SQL_TEXT VERSION_COUNT
------------------------------------------------------------------------- ------------
select object_id,object_name from sharedpool_test where object_id=:v_obj_id 2
SQL> select sql_text,child_address,address from v$sql where hash_value=4086365039;
SQL_TEXT CHILD_ADDRESS ADDRESS
-------------------------------------------------------------------- ----------- --------
select object_id,object_name from sharedpool_test where object_id=:v_obj_id 6757F358 676B6D08
select object_id,object_name from sharedpool_test where object_id=:v_obj_id 674440FC 676B6D08
從記錄父遊標的檢視v$sqlarea的version_count列可以看到,該SQL語句有2個子遊標。而從記錄子游標的檢視v$sql裡可以看到,該SQL文字確實有兩條記錄,而且它們的SQL文字所處的地址(address列)也是一樣的,但是子地址(child_address)卻不一樣。這裡的子地址實際就是子游標所對應的heap 0的控制代碼。
由此我們也可以看到,存在許多因素可能導致SQL語句不能共享。常見的因素包括, SQL文字大小寫不一致、SQL語句的繫結變數的型別不一致、SQL語句涉及到的物件名稱雖然一致但是位於不同的schema下、SQL的優化器模式不一致(比如新增提示、修改了optimizer_mode引數等)等。