淺談cursor_sharing 取值對SQL共享的影響
在Oracle中,使用者應用輸入的SQL語句要進行所謂的Parse解析過程,用於生成執行計劃,這也就是Query Optimizer的主要工作。在Parse中,有兩種具體型別,被稱為“hard parse”(硬解析)和“Soft parse”(軟解析)
“實現執行計劃shared cursor共享,減少硬解析”是我們OLTP系統優化一個重要方向。但是,讓Oracle真正實現SQL共享不是一件容易的事情,受到很多其他因素的影響。最常用的方式是使用繫結變數,讓SQL字面值保持一致。如果應用端沒有使用繫結變數,一種做法是設定系統引數cursor_sharing,將SQL語句中的條件進行繫結變數替換。本篇將從cursor_sharing可選值含義入手,討論分析幾種取值的確切含義和應用場景。以及為什麼很多資料中都是對cursor_sharing設定望而卻步
1.實驗環境
我們在Oracle 11g下準備一個相對偏值的資料表。
SQL> select * from v$version;BANNER
--------------------------------------------------------------------------------
Oracle Database 11g Enterprise Edition Release 11.2.0.4.0 - 64bit Production
PL/SQL Release 11.2.0.4.0 - Production
CORE 11.2.0.4.0 Production
TNS for Linux: Version 11.2.0.4.0 - Production
NLSRTL Version 11.2.0.4.0 - Production
在Oracle 11g裡,預設cursor_sharing取值為EXACT,表示不開啟SQL字面取值繫結變數替換功能。
SQL> show parameter cursor_sharing
NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
cursor_sharing string EXACT
使用指令碼生成資料表資料
Table created.
SQL> create index idx_t_id1 on t(id1);
Index created.
SQL> select object_id from dba_objects where owner='ZYSCM' and object_name='T';
OBJECT_ID
----------
170516
SQL> select id1, count(*) from t group by id1;
ID1 COUNT(*)
---------- ----------
P 11
D 22
A 7
SQL>
2、 統計量收集
這裡單獨談談統計量收集的問題。在Oracle統計量中,通常選擇直方圖histogram進行偏度描述。
注意:在Oracle 9i中,直方圖預設使用dbms_stats是不會收集的,需要手工的制定method_opts引數。在Oracle 10g之後,使用“column auto”作為method_opts引數的預設取值SQL> exec dbms_stats.gather_table_stats(user,'T',cascade => true,method_opt => 'for all columns size auto');
PL/SQL procedure successfully completed.
SQL> select column_name, num_distinct, NUM_BUCKETS, HISTOGRAM from dba_tab_col_statistics where owner='ZYSCM' and table_name='T';
COLUMN_NAME NUM_DISTINCT NUM_BUCKETS HISTOGRAM
------------------------------ ------------ ----------- ---------------
ID1 3 1 NONE
ID2 3 1 NONE
ID3 3 1 NONE
SQL>
注意,預設是沒有生成直方圖的。主要原因在於需要使用一次id1作為條件列。
//使用一次條件列;
SQL> select count(*) from t where id1='D';COUNT(*)
----------
22
//重新收集一下統計量;
SQL> exec dbms_stats.gather_table_stats(user,'T',cascade => true,method_opt => 'for all columns size auto');PL/SQL procedure successfully completed.
//發現統計量收集
SQL> select column_name, num_distinct, NUM_BUCKETS, HISTOGRAM from dba_tab_col_statistics where owner='ZYSCM' and table_name='T';COLUMN_NAME NUM_DISTINCT NUM_BUCKETS HISTOGRAM
------------------------------ ------------ ----------- ---------------
ID1 3 3 FREQUENCY
ID2 3 1 NONE
ID3 3 1 NONE
SQL>
當我們使用過一次id1條件之後,再次手動統計量,使用預設的auto引數,就生成id1列的頻度直方圖。
這裡也就揭示了Oracle在收集統計量直方圖auto選項的含義。當我們指定auto之後,Oracle會自動判斷是否對資料列生成直方圖、生成直方圖bullet的個數。如果這個列從來就沒有出現在SQL條件列中,也就不會被收集直方圖。
3、EXACT——不進行條件列替換
EXACT是cursor_sharing引數的預設選項,表示含義是不進行SQL條件自動繫結變數替換。
SQL> show parameter cursor_sharingNAME TYPE VALUE
------------------------------------ ----------- ------------------------------
cursor_sharing string EXACT
SQL> alter system flush shared_pool;
System altered.
我們發出兩句SQL,分別使用資料取值差異很大的id1值。
SQL> select /*+ cursor_sharing_exact_demo */ count(*) from t where id1='P';COUNT(*)
----------
11
SQL> select /*+ cursor_sharing_exact_demo */ count(*) from t where id1='A';
COUNT(*)
----------
7
此時,父子游標library cache中情況如下:
SQL> select sql_text, sql_id, version_count, executions from v$sqlarea where sql_text like 'select /*+ cursor_sharing_exact_demo */%';SQL_TEXT SQL_ID VERSION_COUNT EXECUTIONS
------------- ----------- ----------- ---------
select /*+ cursor_sharing_exact_demo */ count(*) from t where id1='P' 1fu450v8ddmv9 1 1
select /*+ cursor_sharing_exact_demo */ count(*) from t where id1='A' 16jfmsdnc6as0 1 1
在EXACT下,不會發生SQL字面值改寫的情況。如果兩個SQL的其他部分相同,只是where條件的取值有差異,Oracle是會將這兩個語句作為兩個單獨SQL進行硬解析,分別生成執行計劃。
4、 FORCE——強制共享執行計劃
預設值EXACT的作用是不對非字面SQL繫結變數進行替換操作。而FORCE值和SIMILAR取值意味著Oracle需要對輸入的SQL語句進行處理,首先就是對條件值進行繫結變數化,其次就是針對不同的取值採用不同的執行計劃共享策略。
當選擇FORCE值的時候,意味著Oracle會對SQL字面值進行繫結變數處理。一個語句形成父遊標和僅有的一個子遊標。子游標執行計劃通過Oracle binds peeking技術實現,以後所有類似形態的SQL都是先共享。
SQL> alter system flush shared_pool;System altered.
SQL> alter session set cursor_sharing='FORCE';
Session altered.
SQL> show parameter cursor_sharing
NAME TYPE VALUE
------------------------------------ -------------------------------- ------------------------------
cursor_sharing string FORCE
將當前會話的cursor_sharing設定為force,同時清空library cache。之後使用三條SQL語句進行試驗。
SQL> select /*+ cursor_sharing_force_demo */ count(*) from t where id1='D';COUNT(*)
----------
22
SQL> select /*+ cursor_sharing_force_demo */ count(*) from t where id1='P';
COUNT(*)
----------
11
SQL> select /*+ cursor_sharing_force_demo */ count(*) from t where id1='A';
COUNT(*)
----------
7
如果在EXACT取值的時候,三個執行語句一定會生成三個父遊標和三個子游標的。每一個遊標對應一個單獨的執行計劃。第一和第二條SQL對應全表掃描FTS方案較好,而第三條SQL顯然索引路徑較優。我們看看在FORCE取值的時候,生成計劃情況如何呢?
SQL> select sql_text, sql_id, version_count, executions from v$sqlarea where sql_text like '%/*+ cursor_sharing_force_demo */%';
SQL_TEXT SQL_ID VERSION_COUNT EXECUTIONS
-------------------------------------------------------------------------------- ------------- ------------- ----------
select /*+ cursor_sharing_force_demo */ count(*) from t where id1=:"SYS_B_0" 2yczsvd6tcjuj 1 3
此時,我們觀察到三次執行之後SQL遊標共享情況。首先,三次的SQL語句從字面值上完全不同,差異只是存在在條件id1取值上。如果在cursor_sharing為EXACT模式下,是不能實現遊標共享的。設定為FORCE之後,我們發現Oracle自動將id1=後面的條件替換為繫結變數。三次SQL呼叫均使用相同的父遊標,而子游標只存在一個,意味著三次呼叫均是使用這個唯一的子游標。一個子遊標對應一個執行計劃,三個SQL使用相同的執行計劃。
5、SIMILAR——另一個極端
剛剛我們討論了FORCE。在FORCE下,問題是很簡單的:進行繫結變數替換,共享全部遊標。但是這樣對於資料分佈不均衡的條件列來說,是存在很多問題的。實際環境中會出現SQL效能時好時壞的情況。作為另一個極端,我們設定SIMILAR取值。
SQL> alter system set cursor_sharing='SIMILAR';System altered.
SQL> show parameter cursor_sharing;
NAME TYPE
------------------------------------ --------------------------------
VALUE
------------------------------
cursor_sharing string
SIMILAR
為了更容易看清現象,我們使用逐步試驗的方法:
--Invoke SQL 1
SQL> select /*+ cursor_sharing_similar_demo */ count(*) from t where id1='P';COUNT(*)
----------
11
SQL> select sql_text, sql_id, version_count, executions from v$sqlarea where sql_text like '%/*+ cursor_sharing_similar_demo */%';
SQL_TEXT
--------------------------------------------------------------------------------
SQL_ID VERSION_COUNT EXECUTIONS
------------- ------------- ----------
select /*+ cursor_sharing_similar_demo */ count(*) from t where id1=:"SYS_B_0"
f0rpwa1ja706p 1 1
第一次呼叫SQL語句,使用條件值D。在library cache中生成了父子游標,而且同FORCE一樣,進行了繫結變數替換。下面進行第二次呼叫:
SQL> select /*+ cursor_sharing_similar_demo */ count(*) from t where id1='A';
COUNT(*)
----------
7
SQL_TEXT SQL_ID VERSION_COUNT EXECUTIONS
-------------------------------------------------------------------------------- ------------- ------------- ----------
select sql_text, sql_id, version_count, executions from v$sqlarea where sql_text f0rpwa1ja706p 2 2
like '%/*+ cursor_sharing_force_demo */%'
在使用一個新值A的情況下,生成了一個新的子游標(version_count=2)
6、結論
cursor_sharing的取值和引數是Oracle library cache中管理生成乃至共享執行計劃的重要引數。EXACT值是預設值,實現了直接使用字面SQL不開啟轉變繫結變數的功能。
而FORCE和SIMILAR取值卻開啟了字面轉繫結變數的功能。在這兩個模式下,Oracle會自動的將where後面的條件替換為繫結變數,以增加SQL共享的概率。具體實現sharing的方式上,FORCE和SIMILAR取值又有所差異。
FORCE的sharing原則是共享一切,只生成一個子遊標,之後所有都去共享這個子游標的執行計劃。隨之而來的就是bind peeking問題風險。
而SIMILAR過於謹慎,對每一個SQL都進行類似bind peeking操作。對每個可能取值都生成單獨的子游標執行計劃。相同的輸入共享相同的執行計劃。這個雖然避免了bind peeking問題,卻帶來了新的多version count問題。
從EXACT到FORCE到SIMIlAR,到Oracle 11g中推出的ACS(Adaptive Cursor Sharing),Oracle一直試圖去實現cursor sharing的自動化和高效化。過去,只能通過手工顯示繫結變數來實現SQL共享最大化的目標。而手工書寫的大部分SQL由於字面值的原因很難共享。cursor_sharing引數的作用就是進行這方面的嘗試,雖然從目前看還是有一些問題,但是已經進行了有益的嘗試。
Oracle 11g中推出的ACS自適應遊標,將遊標共享的標準從SQL字面值相同,繫結變數Peeking值相同,拓展到執行計劃相同。在不斷的自適應嘗試過程中,Oracle ACS最終會確定適合的共享方案和執行計劃。