淺談cursor_sharing取值對SQL共享的影響(下)

上篇我們介紹了Oracle SQL共享遊標的機制,以及cursor_sharing參數EXACT取值作用。本篇我們繼續介紹cursor_sharing參數的其他兩個取值:FORCESIMILAR的實際含義。

 

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> select name, value from v$parameter where name='cursor_sharing';

 

NAME                 VALUE

-------------------- --------------------

cursor_sharing       FORCE

 

 

將當前會話的cursor_sharing設置爲force,同時清空library cache。之後使用三條SQL語句進行試驗。

 

 

SQL>  select /*+ cursor_sharing_force_demo */ count(*) from t where id1='D';

 

  COUNT(*)

----------

     10000

 

SQL>  select /*+ cursor_sharing_force_demo */ count(*) from t where id1='P';

 

  COUNT(*)

----------

      8000

 

SQL> select /*+ cursor_sharing_force_demo */ count(*) from t where id1='G';

 

  COUNT(*)

----------

         5

 

 

 

如果在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"     24vkux5z1rsjy             1          3

 

 

SQL> select sql_id, child_number, sql_text from v$sql where sql_id='24vkux5z1rsjy';

 

SQL_ID        CHILD_NUMBER SQL_TEXT

------------- ------------ --------------------------------------------------------------------------------

24vkux5z1rsjy            0 select /*+ cursor_sharing_force_demo */ count(*) from t where id1=:"SYS_B_0"

 

 

此時,我們觀察到三次執行之後SQL遊標共享情況。首先,三次的SQL語句從字面值上完全不同,差異只是存在在條件id1取值上。如果在cursor_sharingEXACT模式下,是不能實現遊標共享的。設置爲FORCE之後,我們發現Oracle自動將id1=後面的條件替換爲綁定變量。三次SQL調用均使用相同的父遊標,而子游標只存在一個,意味著三次調用均是使用這個唯一的子游標。一個子遊標對應一個執行計劃,三個SQL使用相同的執行計劃。

 

我們使用抽取手段抽取出執行計劃,如下:

 

 

SQL> select * from table(dbms_xplan.display_cursor('24vkux5z1rsjy',0,'advanced'));

 

PLAN_TABLE_OUTPUT

--------------------------------------------------------------------------------

SQL_ID  24vkux5z1rsjy, child number 0

-------------------------------------

select /*+ cursor_sharing_force_demo */ count(*) from t where

id1=:"SYS_B_0"

Plan hash value: 2966233522

---------------------------------------------------------------------------

| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |

---------------------------------------------------------------------------

|   0 | SELECT STATEMENT   |      |       |       |     9 (100)|          |

|   1 |  SORT AGGREGATE    |      |     1 |     2 |            |          |

|*  2 |   TABLE ACCESS FULL| T    | 10000 | 20000 |     9  (12)| 00:00:01 |

---------------------------------------------------------------------------

Query Block Name / Object Alias (identified by operation id):

-------------------------------------------------------------

   1 - SEL$1

   2 - SEL$1 / T@SEL$1

 

Peeked Binds (identified by position):

--------------------------------------

   1 - :SYS_B_0 (VARCHAR2(30), CSID=852): 'D'

Predicate Information (identified by operation id):

---------------------------------------------------

   2 - filter("ID1"=:SYS_B_0)

 

49 rows selected

 

 

注意,這個執行計劃進行的是數據表的全表掃描。三次調用中,id1取定D\P因爲數據量大,都可以使用FTS。但是G取值量少,應該使用索引路徑較爲合適。所以,在第一次生成執行計劃之後,Oracle都會使用該執行計劃作爲相同SQL結構的語句計劃。

 

這類問題很類似於大名鼎鼎的bind peeking。在執行計劃中,我們也的確看到了bind peeking信息,說明其中是使用‘D’值peeking出的執行計劃。

 

此處,我們已經可以知道FORCE取值的效果,當cursor_sharing參數選擇FORCE的時候:

 

ü        Oracle對輸入的SQL值,會將where條件取值自動替換爲綁定變量。以後在輸入相同的結構SQL語句時,會進行cursor sharing共享遊標;

ü        在第一次進行自動替換綁定變量的時候,Oracle會依據bind peeking取值,獲取到一個執行計劃,對應成子游標;

ü        在以後的SQL語句中,只要出現父遊標可共享的情況,Oracle都會強制使用生成的唯一子游標進行sharing。不去在乎是不是對SQL是最優的執行計劃;

 

FORCE取值的規則思想很簡單,SQL語句進行強制的綁定變量替換。使用第一次的bind peeking值生成執行計劃,之後全部使用這個執行計劃。這種方式實現了遊標共享,避免出現大量的library cache硬解析,限制一個父遊標的version_count數量。

 

如果這種SQL語句本身是“Good SQL”,也就是條件列分佈比較平均,沒有出現過大的偏移分佈。我們認爲這種FORCE是很有益的。但是如果數據列分佈不平均,這樣借用第一次輸入的bind peeking生成並且共享執行計劃就很成問題。我們說,在cursor_sharing取定FORCE遇到的潛在問題,和我們使用綁定變量時候使用的bind peeking值問題是相同的。

 

 

5SIMILAR——另一個極端

 

剛剛我們討論了FORCE。在FORCE下,問題是很簡單的:進行綁定變量替換,共享全部遊標。但是這樣對於數據分佈不均衡的條件列來說,是存在很多問題的。實際環境中會出現SQL性能時好時壞的情況。作爲另一個極端,我們設置SIMILAR取值。

 

 

SQL> alter session set cursor_sharing='SIMILAR';

Session altered

 

SQL> show parameter cursor_sharing;

 

NAME                                 TYPE        VALUE

------------------------------------ ----------- ------------------------------

cursor_sharing                       string      EXACT

 

SQL> select name, value from v$parameter where name='cursor_sharing';

NAME                 VALUE

-------------------- --------------------

cursor_sharing       SIMILAR

 

 

爲了更容易看清現象,我們使用逐步試驗的方法:

 

--Invoke SQL 1

SQL> select /*+ cursor_sharing_similar_demo */ count(*) from t where id1='D';

 

  COUNT(*)

----------

     10000

 

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"   0s63s6sjytz4y             1          1

 

 

第一次調用SQL語句,使用條件值D。在library cache中生成了父子游標,而且同FORCE一樣,進行了綁定變量替換。我們抽出執行計劃進行查看。

 

 

SQL> select * from table(dbms_xplan.display_cursor('0s63s6sjytz4y',0,'advanced'));

 

PLAN_TABLE_OUTPUT

--------------------------------------------------------------------------------

SQL_ID  0s63s6sjytz4y, child number 0

-------------------------------------

select /*+ cursor_sharing_similar_demo */ count(*) from t where

id1=:"SYS_B_0"

Plan hash value: 2966233522

---------------------------------------------------------------------------

| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |

---------------------------------------------------------------------------

|   0 | SELECT STATEMENT   |      |       |       |     9 (100)|          |

|   1 |  SORT AGGREGATE    |      |     1 |     2 |            |          |

|*  2 |   TABLE ACCESS FULL| T    | 10000 | 20000 |     9  (12)| 00:00:01 |

---------------------------------------------------------------------------

Peeked Binds (identified by position):

--------------------------------------

   1 - :SYS_B_0 (VARCHAR2(30), CSID=852): 'D'

Predicate Information (identified by operation id):

---------------------------------------------------

 

49 rows selected

 

 

對子游標(child_number=0)而言,使用D進行bind peeking後,生成全表掃描的執行計劃。下面進行第二次調用:

 

 

SQL> select /*+ cursor_sharing_similar_demo */ count(*) from t where id1='G';

 

  COUNT(*)

----------

         5

 

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"   0s63s6sjytz4y             2          2

 

 

在使用一個新值G的情況下,生成了一個新的子游標(version_count=2)。我們抽取出該執行計劃進行查看。

 

 

SQL> select * from table(dbms_xplan.display_cursor('0s63s6sjytz4y',1,'advanced'));

 

PLAN_TABLE_OUTPUT

--------------------------------------------------------------------------------

SQL_ID  0s63s6sjytz4y, child number 1

-------------------------------------

select /*+ cursor_sharing_similar_demo */ count(*) from t where

id1=:"SYS_B_0"

Plan hash value: 555228874

-------------------------------------------------------------------------------

| Id  | Operation         | Name      | Rows  | Bytes | Cost (%CPU)| Time     |

-------------------------------------------------------------------------------

|   0 | SELECT STATEMENT  |           |       |       |     1 (100)|          |

|   1 |  SORT AGGREGATE   |           |     1 |     2 |            |          |

|*  2 |   INDEX RANGE SCAN| IDX_T_ID1 |     5 |    10 |     1   (0)| 00:00:01 |

-------------------------------------------------------------------------------

Peeked Binds (identified by position):

--------------------------------------

   1 - :SYS_B_0 (VARCHAR2(30), CSID=852): 'G'

Predicate Information (identified by operation id):

---------------------------------------------------

 

49 rows selected

 

 

在使用一個新的變量值G的情況下,Oracle生成了一個新的遊標執行計劃作爲對應。新生成的執行計劃是使用索引路徑。下面進行第三次調用。

 

 

SQL> select /*+ cursor_sharing_similar_demo */ count(*) from t where id1='D';

 

  COUNT(*)

----------

     10000

 

 

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"   0s63s6sjytz4y            2          3

 

 

第三次執行,我們選擇了第一次執行使用過的D值。此時,我們發現executions次數增加一次,但是version_count版本數量沒有增加。可以知道使用了第一次生成的child_number=0的子游標執行計劃。也就是說,當使用相同的綁定變量值的時候,Oracle會共享子游標。

 

第四次執行,使用一個新的對應值。

 

 

SQL> select /*+ cursor_sharing_similar_demo */ count(*) from t where id1='P';

 

  COUNT(*)

----------

      8000

 

 

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"   0s63s6sjytz4y             3          4

 

 

SQL> select sql_text,sql_id, child_number, LAST_LOAD_TIME from v$sql where sql_id='0s63s6sjytz4y';

 

SQL_TEXT                                                                         SQL_ID        CHILD_NUMBER LAST_LOAD_TIME

-------------------------------------------------------------------------------- ------------- ------------ --------------------------------------

select /*+ cursor_sharing_similar_demo */ count(*) from t where id1=:"SYS_B_0"   0s63s6sjytz4y            0 2011-07-30/19:25:31

select /*+ cursor_sharing_similar_demo */ count(*) from t where id1=:"SYS_B_0"   0s63s6sjytz4y            1 2011-07-30/19:26:40

select /*+ cursor_sharing_similar_demo */ count(*) from t where id1=:"SYS_B_0"   0s63s6sjytz4y            2 2011-07-30/19:29:42

 

 

 

對於一個新的綁定變量取值POracle進行bind peeking之後生成了一個新的子游標(child_number=2)與之對應。也就是生成了一個新的執行計劃:

 

 

SQL> select * from table(dbms_xplan.display_cursor('0s63s6sjytz4y',2,'advanced'));

 

PLAN_TABLE_OUTPUT

--------------------------------------------------------------------------------

SQL_ID  0s63s6sjytz4y, child number 2

-------------------------------------

select /*+ cursor_sharing_similar_demo */ count(*) from t where

id1=:"SYS_B_0"

Plan hash value: 2966233522

---------------------------------------------------------------------------

| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |

---------------------------------------------------------------------------

|   0 | SELECT STATEMENT   |      |       |       |     9 (100)|          |

|   1 |  SORT AGGREGATE    |      |     1 |     2 |            |          |

|*  2 |   TABLE ACCESS FULL| T    |  8000 | 16000 |     9  (12)| 00:00:01 |

---------------------------------------------------------------------------

Peeked Binds (identified by position):

--------------------------------------

   1 - :SYS_B_0 (VARCHAR2(30), CSID=852): 'P'

 

49 rows selected

 

 

雖然child_number=2的執行計劃也是使用全表掃描的執行計劃,本質上同child_number=0的性質相同。但是Oracle沒有進行共享,而是重新爲這個bind peeking生成了一個新的子游標。

 

下面繼續執行SQL

 

 

SQL> select /*+ cursor_sharing_similar_demo */ count(*) from t where id1='A';

 

  COUNT(*)

----------

        10

 

 

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"   0s63s6sjytz4y             4          5

 

 

SQL> select sql_text,sql_id, child_number, LAST_LOAD_TIME,executions from v$sql where sql_id='0s63s6sjytz4y';

 

SQL_TEXT                                                                         SQL_ID        CHILD_NUMBER LAST_LOAD_TIME                         EXECUTIONS

-------------------------------------------------------------------------------- ------------- ------------ -------------------------------------- ----------

select /*+ cursor_sharing_similar_demo */ count(*) from t where id1=:"SYS_B_0"   0s63s6sjytz4y            0 2011-07-30/19:25:31                             2

select /*+ cursor_sharing_similar_demo */ count(*) from t where id1=:"SYS_B_0"   0s63s6sjytz4y            1 2011-07-30/19:26:40                             1

select /*+ cursor_sharing_similar_demo */ count(*) from t where id1=:"SYS_B_0"   0s63s6sjytz4y            2 2011-07-30/19:29:42                             1

select /*+ cursor_sharing_similar_demo */ count(*) from t where id1=:"SYS_B_0"   0s63s6sjytz4y            3 2011-07-30/19:32:12                             1

 

 

根據一個新的綁定變量值AOracle又爲其生成了一個新的執行計劃。

 

至此,我們可以初步猜出SIMILAR的特徵,相對於FORCE取值帶來的問題,SIMILAR往靈活的方向前進了一步:

 

ü        cursor_sharing設置爲SIMILAR的時候,Oracle對沒有使用綁定變量的SQL字面語句都會進行處理,將where條件後自動替換爲綁定變量;

ü        在執行語句是,對每一個條件設置值,都會生成一個新的child cursor子游標,與父遊標相對應。也就意味着對每一個語句,都會發生一次隱式的bind peeking動作;

ü        當一個語句輸入的時候,如果之前存在過相同條件值的SQL子游標,就共享該子游標。否則生成一個新的child cursor,生成一個匹配的執行計劃;

 

SIMILARFORCE的進化版。在SIMILAR模式下,Oracle對遊標共享的條件變得比較敏感。如果綁定變量值發生變化,就意味着執行計劃可能存在不匹配的情況。所以索性Oracle對每一個新的值都bind peeking一下,生成執行計劃。而執行計劃遊標的共享只在相同綁定變量的時候才發生。

 

這個與FORCE相比,cursor_sharing=SIMILAR的確緩解了由於bind peeking單次帶來的執行計劃不匹配問題。但是會引入兩個新問題:

 

ü        如果對應條件列的取值相對較少,這樣生成執行計劃的個數起碼是可以控制的。如果是一種連續取值情況或者對應取值很多,必然引起parent cursor對應的child cursor數目增多,每次從child cursor列中遍歷的時間增加,latchpin發生的時間增多。這也是similar取值是一個常見的問題;

ü        生成child cursor的標準不是是否執行計劃相同,而是綁定變量值相同。這樣如果數據分佈較爲平均,所有值對應的執行計劃都是相同的。那麼生成很多的子游標執行計劃必然是相同的。這樣又會帶來性能和其他一些問題。

 

 

6、結論

 

cursor_sharing的取值和參數是Oracle library cache中管理生成乃至共享執行計劃的重要參數。EXACT值是默認值,實現了直接使用字面SQL不開啓轉變綁定變量的功能。

 

FORCESIMILAR取值卻開啓了字面轉綁定變量的功能。在這兩個模式下,Oracle會自動的將where後面的條件替換爲綁定變量,以增加SQL共享的概率。具體實現sharing的方式上,FORCESIMILAR取值又有所差異。

 

FORCEsharing原則是共享一切,只生成一個子遊標,之後所有都去共享這個子游標的執行計劃。隨之而來的就是bind peeking問題風險。

 

SIMILAR過於謹慎,對每一個SQL都進行類似bind peeking操作。對每個可能取值都生成單獨的子游標執行計劃。相同的輸入共享相同的執行計劃。這個雖然避免了bind peeking問題,卻帶來了新的多version count問題。

 

 

筆者認爲:從EXACTFORCESIMIlAR,到Oracle 11g中推出的ACSAdaptive Cursor Sharing),Oracle一直試圖去實現cursor sharing的自動化和高效化。過去,只能通過手工顯示綁定變量來實現SQL共享最大化的目標。而手工書寫的大部分SQL由於字面值的原因很難共享。cursor_sharing參數的作用就是進行這方面的嘗試,雖然從目前看還是有一些問題,但是已經進行了有益的嘗試。

 

Oracle 11g中推出的ACS自適應遊標,將遊標共享的標準從SQL字面值相同,綁定變量Peeking值相同,拓展到執行計劃相同。在不斷的自適應嘗試過程中,Oracle ACS最終會確定適合的共享方案和執行計劃。

 

關於ACS的相關內容,請參考筆者的《Oracle自適應共享遊標——Adaptive Cursor Sharing》(http://space.itpub.net/17203031/viewspace-703280)系列文章。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章