1. 程式人生 > >淺談cursor_sharing 取值對SQL共享的影響

淺談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

使用指令碼生成資料表資料

SQL> create table t (id1 varchar2(10), id2 varchar2(10), id3 varchar2(10));

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_sharing

NAME                     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> 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 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最終會確定適合的共享方案和執行計劃。