Oracle綁定變量分級(Bind Graduation)

Oracle綁定變量分級(Bind Graduation)

綁定變量分級(Bind Graduation)是指Oracle在PL/SQL代碼中會根據文本型綁定變量的定義長度而將這些文本型綁定變量分爲四個等級,以下所示:sql

l 定義長度小於等於32字節(Byte)的文本型綁定變量被分在第一個等級,Oracle爲其分配32字節的內存空間。數據庫

l 定義長度在[33,128]字節之間的被分在第二個等級,Oracle爲其分配128字節的內存空間。session

l 定義長度在[129,2000]字節之間的文本型綁定變量被分在第三個等級,Oracle爲其分配2000字節的內存空間。併發

l 定義長度在2000字節以上被分在第四個等級,Oracle爲此等級的文本型綁定變量分配的內存空間大小取決於對應文本型綁定變量所傳入的實際綁定變量值的大小。若是實際傳入的綁定變量值小於或等於2000字節,那麼Oracle會爲其分配2000字節的內存空間。若是實際傳入的綁定變量值大於2000字節,那麼Oracle會爲其分配4000字節的內存空間。oracle

須要注意的是,綁定變量分級僅適用於文本型的綁定變量,Oracle不會對數值(NUMBER)型的綁定變量作綁定變量分級。Oracle數據庫中數值型的變量最大隻能佔用22字節,因此對於數值型的綁定變量而言,Oracle統一爲其分配了22字節的內存空間。ide

若是在PL/SQL代碼中使用了文本型綁定變量,只要其SQL文本中文本型綁定變量的定義長度發生了變化,那麼Oracle爲這些綁定變量所分配的內存空間的大小也可能會隨之發生變化,而一旦Oracle爲這些綁定變量所分配的內存空間的大小發生了變化,那麼該SQL以前存儲在Child Cursor中的解析樹和執行計劃就不能被重用了。其緣由是Child Cursor中除了會存儲目標SQL的解析樹和執行計劃以外,還會存儲該SQL所使用的綁定變量的類型和長度,這意味着即便該SQL的SQL文本沒有發生任何改變,只要其SQL文本中文本型綁定變量的定義長度發生了變化,那麼該SQL再次執行時就可能仍是作硬解析(新生成一個子遊標)。性能

下面給出一個示例(數據庫版本爲11.2.0.3):測試

建表T_BG_20170610_LHR,並給出5個PL/SQL代碼:優化

CREATE TABLE T_BG_20170610_LHR(N NUMBER(10),V VARCHAR2(3000));orm

--SQL_TEXT1:硬解析

DECLARE

  N NUMBER(10) :=1;--分配22字節的內存空間

  V VARCHAR2(32) :='XIAOMAIMIAO1';--分配32字節的內存空間

BEGIN

  EXECUTE IMMEDIATE 'INSERT INTO T_BG_20170610_LHR VALUES(:N,:V)' USING N, V;

  COMMIT;

END;

/

--SQL_TEXT2:硬解析

DECLARE

  N NUMBER(10) :=2;--分配22字節的內存空間

  V VARCHAR2(33) :='XIAOMAIMIAO2';--分配128字節的內存空間

BEGIN

  EXECUTE IMMEDIATE 'INSERT INTO T_BG_20170610_LHR VALUES(:N,:V)' USING N, V;

  COMMIT;

END;

/

 

--SQL_TEXT3:硬解析

DECLARE

  N NUMBER(10) :=3;--分配22字節的內存空間

  V VARCHAR2(129) :='XIAOMAIMIAO3';--分配2000字節的內存空間

BEGIN

  EXECUTE IMMEDIATE 'INSERT INTO T_BG_20170610_LHR VALUES(:N,:V)' USING N, V;

  COMMIT;

END;

/

 

--SQL_TEXT4:軟解析

DECLARE

  N NUMBER(10) :=4;--分配22字節的內存空間

  V VARCHAR2(2001) :='XIAOMAIMIAO4';--分配2000字節的內存空間

BEGIN

  EXECUTE IMMEDIATE 'INSERT INTO T_BG_20170610_LHR VALUES(:N,:V)' USING N, V;

  COMMIT;

END;

/

--SQL_TEXT5:軟解析

DECLARE

  N NUMBER(10) :=5;--分配22字節的內存空間

  V VARCHAR2(32767) :='XIAOMAIMIAO5';--分配2000字節的內存空間

BEGIN

  EXECUTE IMMEDIATE 'INSERT INTO T_BG_20170610_LHR VALUES(:N,:V)' USING N, V;

  COMMIT;

END;

/

--SQL_TEXT6: 硬解析

DECLARE

  N NUMBER(10) :=6;  --分配22字節的內存空間

  V VARCHAR2(32767) :=RPAD('XIAOMAIMIAO6',2002,'8');  --字符串長度爲2002,分配4000字節的內存空間

BEGIN

  EXECUTE IMMEDIATE 'INSERT INTO T_BG_20170610_LHR VALUES(:N,:V)' USING N, V;

  COMMIT;

END;

/

執行上述建表語句和PL/SQL代碼,查看結果:

LHR@orclasm > col v format a13

LHR@orclasm > select * from T_BG_20170610_LHR T WHERE T.N<=5;

 

         N V

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

         1 XIAOMAIMIAO1

         2 XIAOMAIMIAO2

         3 XIAOMAIMIAO3

         4 XIAOMAIMIAO4

         5 XIAOMAIMIAO5

LHR@orclasm > SELECT T.N,LENGTH(T.V) FROM T_BG_20170610_LHR T;

 

         N LENGTH(T.V)

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

         1          12

         2          12

         3          12

         4          12

         5          12

         6        2002

一旦Oracle爲這些文本型綁定變量所分配的內存空間的大小發生了變化,那麼該SQL以前存儲在Child Cursor中的解析樹和執行計劃就不能被重用了。因此這裏Oracle在執行範例PL/SQL代碼一、二、3時每次都是硬解析,但在執行範例PL/SQL代碼4和5時會用軟解析/軟軟解析,由於範例PL/SQL代碼4和5能夠重用以前執行的範例PL/SQL代碼3中目標SQL(即INSERT INTO T_BG_20170610_LHR VALUES(:N,:V))的解析樹和執行計劃。在執行範例PL/SQL代碼6時是硬解析,這意味着對於此目標SQL而言,其所在的Parent cursor下會有4個Child Cursor:

LHR@orclasm > col sql_text format a60

LHR@orclasm > SELECT SQL_TEXT,SQL_ID,VERSION_COUNT,EXECUTIONS FROM V$SQLAREA WHERE SQL_TEXT LIKE 'INSERT INTO T_BG_20170610_LHR VALUES%';

 

SQL_TEXT                                                     SQL_ID        VERSION_COUNT EXECUTIONS

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

INSERT INTO T_BG_20170610_LHR VALUES(:N,:V)                  01g03pruhphqc             4          6

 

 

LHR@orclasm > SELECT SQL_TEXT,SQL_ID,D.CHILD_NUMBER,D.CHILD_ADDRESS,EXECUTIONS FROM V$SQL D WHERE SQL_ID = '01g03pruhphqc';

 

SQL_TEXT                                                     SQL_ID        CHILD_NUMBER CHILD_ADDRESS    EXECUTIONS

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

INSERT INTO T_BG_20170610_LHR VALUES(:N,:V)                  01g03pruhphqc            0 00000000AA902CE8          1    <<----對應PL/SQL代碼1

INSERT INTO T_BG_20170610_LHR VALUES(:N,:V)                  01g03pruhphqc            1 00000000AAA47348          1    <<----對應PL/SQL代碼2

INSERT INTO T_BG_20170610_LHR VALUES(:N,:V)                  01g03pruhphqc            2 00000000AAAF7A28          3    <<----對應PL/SQL代碼三、四、5

INSERT INTO T_BG_20170610_LHR VALUES(:N,:V)                  01g03pruhphqc            3 0000000095DA4B00          1    <<----對應PL/SQL代碼6

 

LHR@orclasm > SELECT d.SQL_ID,D.CHILD_NUMBER,D.BIND_LENGTH_UPGRADEABLE FROM V$SQL_SHARED_CURSOR D WHERE D.SQL_ID = '01g03pruhphqc';

 

SQL_ID        CHILD_NUMBER B

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

01g03pruhphqc            0 N

01g03pruhphqc            1 Y

01g03pruhphqc            2 Y

01g03pruhphqc            3 Y

下面查詢分配的內存空間大小:

LHR@orclasm > SELECT B.CHILD_NUMBER,B.CHILD_ADDRESS,D.BIND_NAME,D.POSITION,D.DATATYPE,D.MAX_LENGTH FROM v$sql_bind_metadata d,V$SQL b WHERE d.ADDRESS=b.CHILD_ADDRESS AND b.SQL_ID='01g03pruhphqc' ORDER BY B.CHILD_NUMBER,D.POSITION;

 

CHILD_NUMBER CHILD_ADDRESS    BIND_NAME                        POSITION   DATATYPE MAX_LENGTH

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

           0 00000000AA902CE8 N                                       1          2         22

           0 00000000AA902CE8 V                                       2          1         32

           1 00000000AAA47348 N                                       1          2         22

           1 00000000AAA47348 V                                       2          1        128

           2 00000000AAAF7A28 N                                       1          2         22

           2 00000000AAAF7A28 V                                       2          1       2000

           3 0000000095DA4B00 N                                       1          2         22

           3 0000000095DA4B00 V                                       2          1       4000

從上述查詢結果能夠看到,Child Cursor 0中文本型綁定變量V確實被分配了32字節的內存空間,Child Cursor 1中文本型綁定變量V確實被分配了128字節的內存空間,Child Cursor 2中文本型綁定變量V被分配了2000字節的內存空間,Child Cursor 3中文本型綁定變量V被分配了4000字節的內存空間,同時這三個Child Cursor中的數值型綁定變量N統一被分配了22字節的內存空間。

經過上述示例能夠看出:爲了不沒必要要的硬解析,在PL/SQL代碼中處理帶文本型綁定變量的目標SQL時,應該將這些文本型綁定變量的定義長度保持在同一個等級,固然,這裏最好是定義成一個統一的長度,好比VARCHAR2(4000)。

 





綁定變量分級是oracle在pl/sql中會根據文本類型的綁定變量的定義長度而將這些文本型綁定變量分爲四個等級:

1 定義長度在32字節(bytes)之內的文本型綁定變量被分在第一個等級,oracle爲其分配32字節的內存空間
2 定義長度在33-128字節之間的被分爲第二個等級,oracle爲其分配128字節的內存空間
3 定義長度在129-2000字節之間的文本型被分爲在第三個等級,oracle爲其分配2000字節的內存空間
4 定義長度在2000字節以上的被分爲在第四個等級,取決於對應文本類型的綁定變量鎖傳入的實際綁定變量的大小,若是實際傳入的綁定變量的值小於或者等於2000,oracle爲其分配2000字節的內存空間,若是實際傳入的綁定變量大於2000字節,則oracle爲其分配4000字節的內存空間

須要注意的是這裏的綁定變量分級僅僅適用於文本類型的綁定變量,對於number類型的綁定變量oracle統一爲其分配22字節的內存空間。

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

SQL_TEXT1:
declare
n number(10);
v varchar2(32);
begin
n:=1;
v:='xiaoyu';
execute immediate 'insert into t values(:n,:v)' using n,v;
commit;
end;
/

SQL_TEXT2:
declare
n number(10);
v varchar2(80);
begin
n:=1;
v:='xiaoyu';
execute immediate 'insert into t values(:n,:v)' using n,v;
commit;
end;
/

SQL_TEXT3:
declare
n number(10);
v varchar2(2000);
begin
n:=1;
v:='xiaoyu';
execute immediate 'insert into t values(:n,:v)' using n,v;
commit;
end;
/

SQL_TEXT4:
declare
n number(10);
v varchar2(4000);
begin
n:=1;
v:='xiaoyu';
execute immediate 'insert into t values(:n,:v)' using n,v;
commit;
end;
/

SQL_TEXT5:
declare
n number(10);
v varchar2(4000);
begin
n:=1;
v:=rpad('xiaoyu',3000);
execute immediate 'insert into t values(:n,:v)' using n,v;
commit;
end;
/

SQL> select sql_text,sql_id,version_count,executions from v$sqlarea where sql_text like 'insert into t%';

SQL_TEXT                                 SQL_ID        VERSION_COUNT EXECUTIONS
---------------------------------------- ------------- ------------- ----------
insert into t values(:n,:v)              21mycdpm39kzv             3          5

SQL> select sql_id,child_number,child_address,executions from v$sql where sql_id='21mycdpm39kzv';

SQL_ID        CHILD_NUMBER CHILD_ADDRESS    EXECUTIONS
------------- ------------ ---------------- ----------
21mycdpm39kzv            0 000000008A758A48          1
21mycdpm39kzv            1 000000008A755890          3
21mycdpm39kzv            2 000000008A753240          1

SQL> select address,bind_name,position,datatype,max_length from v$sql_bind_metadata where address in ('000000008A758A48','000000008A755890','000000008A753240') order by address,position;

ADDRESS          BIND_NAME                        POSITION   DATATYPE MAX_LENGTH
---------------- ------------------------------ ---------- ---------- ----------
000000008A753240 N                                       1          2         22
000000008A753240 V                                       2          1       4000
000000008A755890 N                                       1          2         22
000000008A755890 V                                       2          1        128
000000008A758A48 N                                       1          2         22
000000008A758A48 V                                       2          1         32

6 rows selected.

本身在11.2.0.4環境中對於SQL語句測試綁定變量分級,發現並不絕對遵照上面的規則來生成child cursor,固然仍是建議SQL語句或者PL/SQL中對於相同的綁定變量,申明的數據類型和長度必須統一,避免由於綁定變量分級而產生過多的child cursor。其緣由是由於child cursor不只僅存儲瞭解析樹和執行計劃,還會存儲該SQL所使用的綁定變量類型和長度,若是該SQL的綁定變量類型和長度發生變化,則oracle將沒法重用該child cursor,必須爲該SQL再生成一個child cursor。

綁定變量什麼時候被捕獲:
1 含有綁定變量的sql語句被硬解析時
2 當含有綁定變量的sql語句以軟解析或者軟軟解析方式重複執行時,該sql語句中的綁定變量的具體輸入值也可能被oracle捕獲,只不過默認狀況下這種捕獲操做oracle須要間隔15分鐘纔會作一次

須要注意的是oracle只會捕獲那些位於目標sql語句的where條件中的綁定變量的具體輸入值,而對於那些使用了綁定變量的insert語句,無論insert語句是否以硬解析方式執行,oracle始終不會捕獲其value子句中對應的綁定變量的具體輸入值。

SQL> variable vnum number;
SQL> exec :vnum:=100;

PL/SQL procedure successfully completed.

SQL> insert into t100 values(:vnum,'acb');

1 row created.

SQL> select sql_id,sql_text from v$sql where sql_text like 'insert into t100%';

SQL_ID
-------------
SQL_TEXT
--------------------------------------------------------------------------------
5cyjjwy6y74t3
insert into t100 values(:vnum,'acb')

SQL> select sql_id,name,position,datatype_string,last_captured,value_string from v$sql_bind_capture where sql_id='5cyjjwy6y74t3';

SQL_ID        NAME                   POSITION DATATYPE_STRING                                              LAST_CAPTURED       VALUE_STRING
------------- -------------------- ---------- ------------------------------------------------------------ ------------------- ------------------------------
5cyjjwy6y74t3 :VNUM                         1 NUMBER

這裏看出來即便該sql語句是硬解析,因爲綁定變量不在sql語句的where條件中,而且是insert into values的語句,oracle並不會捕獲values後面綁定變量的值。

而若是咱們換成下面這種形式,不以insert into values的形式,則oracle是能夠抓取到where後面的綁定變量的具體值的:
SQL> insert into t100 select id,name from t100 where id=:vnum;

2 rows created.

SQL> select sql_id,sql_text from v$sql where sql_text like 'insert into t100 select id,name from t100 where id=:vnum';

SQL_ID
-------------
SQL_TEXT
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
a9v1314t50dwg
insert into t100 select id,name from t100 where id=:vnum


SQL> select sql_id,name,policesition,datatype_string,last_captured,value_string from v$sql_bind_capture where sql_id='a9v1314t50dwg';

SQL_ID        NAME                   POSITION DATATYPE_STRING                                              LAST_CAPTURED       VALUE_STRING
------------- -------------------- ---------- ------------------------------------------------------------ ------------------- ------------------------------
a9v1314t50dwg :VNUM                         1 NUMBER                                                       2015-07-01 01:37:50 100

咱們從新設置下綁定變量的具體值,再次執行上述sql,這裏oracle並無立刻捕獲sql語句中新的綁定變量的具體值,這個是由於軟解析和如軟軟解析時,oracle爲了考慮性能等問題須要間隔15分鐘後才能再次捕獲綁定變量的具體值:

SQL> exec :vnum:=1;

PL/SQL procedure successfully completed.

SQL> insert into t100 select id,name from t100 where id=:vnum;

1 row created.

SQL> select sql_id,name,position,datatype_string,last_captured,value_string from v$sql_bind_capture where sql_id='a9v1314t50dwg';

SQL_ID        NAME                   POSITION DATATYPE_STRING                                              LAST_CAPTURED       VALUE_STRING
------------- -------------------- ---------- ------------------------------------------------------------ ------------------- ------------------------------
a9v1314t50dwg :VNUM                         1 NUMBER                                                       2015-07-01 01:37:50 100

過了15分鐘後再次執行sql,此時這個sql雖然是軟解析,oracle仍是會再次捕獲這個綁定變量的傳入值:
SQL> exec :vnum:=1;

PL/SQL procedure successfully completed.

SQL> insert into t100 select id,name from t100 where id=:vnum;

2 rows created.

SQL> select sql_id,name,position,datatype_string,last_captured,value_string from v$sql_bind_capture where sql_id='a9v1314t50dwg';

SQL_ID        NAME                   POSITION DATATYPE_STRING                                              LAST_CAPTURED       VALUE_STRING
------------- -------------------- ---------- ------------------------------------------------------------ ------------------- ------------------------------
a9v1314t50dwg :VNUM                         1 NUMBER                                                       2015-07-01 01:53:05 1

這裏提到關於綁定變量的問題,這裏再提一下何種謂詞適合用綁定變量:通常而言對於主鍵、id、流水號等基本惟一的值,而且對應的SQL執行頻率特別高,oracle建議使用綁定變量,而對於那些狀態值(例如可以枚舉的,不一樣值只有幾個或者數十個)不建議使用綁定變量,由於這類SQL即便不使用綁定變量,oracle的解析消耗也是微乎其微,而若是使用綁定變量,會致使在某些特定場景下SQL執行計劃並非最合理的,甚至是很是糟糕的,因此也不要將系統中全部的SQL都使用綁定變量,這裏簡單作個測試以供你們參考:

SQL> select * from v$version where rownum=1;

BANNER
--------------------------------------------------------------------------------
Oracle Database 11g Enterprise Edition Release 11.2.0.4.0 - 64bit Production
SQL> create table t01 as select object_id,object_name,object_type from dba_objects;

Table created.
SQL> update t01 set object_type='INDEX' where object_id<90000;

86872 rows updated.

SQL> commit;

Commit complete.

這裏模擬出object_type列數據分佈存在傾斜性

SQL> select object_type,count(*) from t01 group by object_type;

OBJECT_TYPE           COUNT(*)
------------------- ----------
INDEX PARTITION             21
TABLE PARTITION             21
DATABASE LINK                2
LOB                          1
TRIGGER                      1
INDEX                    86894
TABLE                       34
VIEW                         2

8 rows selected.
SQL> create index ind_t01_objtype on t01(object_type);

Index created.
SQL> execute dbms_stats.gather_table_stats(ownname=>'SYS',tabname=>'T01',method_opt=>'FOR ALL COLUMNS SIZE 254');

PL/SQL procedure successfully completed.
SQL> alter session set "_optim_peek_user_binds"=false;

Session altered.

這裏關閉掉綁定變量窺視,其實xiaoyu我的以爲在oracle 11g以前都應該關閉掉綁定變量窺視,而在oracle 11g以後推出了自適應遊標,部分系統能夠考慮開啓窺視和自適應遊標,可是xiaoyu維護的不少大型在線系統對於這兩個特性都是選擇禁掉,自適應遊標有較多的bug,具體能夠參考mos上的部分文章。
SQL> variable type varchar2(128);
SQL> exec :type:='INDEX';

PL/SQL procedure successfully completed.
SQL> select * from t01 where object_type=:type;

86894 rows selected.


Execution Plan
----------------------------------------------------------
Plan hash value: 2537206454

-----------------------------------------------------------------------------------------------
| Id  | Operation                   | Name            | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |                 | 10872 |   382K|    93   (0)| 00:00:02 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T01             | 10872 |   382K|    93   (0)| 00:00:02 |
|*  2 |   INDEX RANGE SCAN          | IND_T01_OBJTYPE | 10872 |       |    26   (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("OBJECT_TYPE"=:TYPE)


Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
      12259  consistent gets
          0  physical reads
          0  redo size
    4607541  bytes sent via SQL*Net to client
      64235  bytes received via SQL*Net from client
       5794  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
      86894  rows processed

SQL> select * from t01 where object_type='INDEX';

86894 rows selected.


Execution Plan
----------------------------------------------------------
Plan hash value: 3295674804

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      | 86928 |  3056K|   144   (1)| 00:00:02 |
|*  1 |  TABLE ACCESS FULL| T01  | 86928 |  3056K|   144   (1)| 00:00:02 |
--------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("OBJECT_TYPE"='INDEX')


Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
       6277  consistent gets
          0  physical reads
          0  redo size
    3342833  bytes sent via SQL*Net to client
      64235  bytes received via SQL*Net from client
       5794  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
      86894  rows processed

對於object_type=’INDEX’優化器更應該選擇全表掃描,而不該該選擇索引範圍掃描,可是由於使用的綁定變量,而後又關閉了綁定變量窺視,則優化器計算選擇率時只能是1/num_distinct,進而致使rows評估也存在較大的偏差,從而選擇了資源消耗更多的索引範圍掃描;若是開啓了窺視,又沒有自適應遊標等特性支撐,會致使後續的SQL都使用以前的執行計劃,又是一個性能隱患。

使用綁定變量字段類型: xiaoyu我的是不太建議狀態字段、時間範圍字段等使用綁定變量的,這類SQL若是執行頻率又不高,不存在較大的併發,即便不寫成綁定變量,也只有幾十個sql_id在shared pool中,這點解析消耗微乎其微,而若是走錯一個不合理的執行計劃,則消耗的資源將遠遠超過這點解析消耗。

相關文章
相關標籤/搜索