【DB筆試面試582】在Oracle中,什麼是綁定變數窺探(上)?
- 2019 年 10 月 10 日
- 筆記
♣
題目部分
在Oracle中,什麼是綁定變數窺探(上)?
♣
答案部分
目標SQL若不使用綁定變數,則當具體輸入值一旦發生了變化,目標SQL的SQL文本就會隨之發生變化,這樣Oracle就能很容易地計算出對應Selectivity和Cardinality的值,進而據此來選擇執行計劃。但對於使用了綁定變數的目標SQL而言,情況就完全不一樣了,因為現在無論對應綁定變數的具體輸入值是什麼,目標SQL的SQL文本都是一模一樣的。對於使用了綁定變數的目標SQL而言,Oracle可以選擇如下兩種方法來決定其執行計劃:
l 使用綁定變數窺探(Bind Peeking)。
l 如果不使用綁定變數窺探,那麼對於那些可選擇率可能會隨著具體輸入值的不同而不同的謂詞條件使用默認的可選擇率(例如5%)
綁定變數窺探(Bind Peeking)是在Oracle 9i中引入的,是否啟用綁定變數窺探受隱含參數「_OPTIM_PEEK_USER_BINDS」的控制,該參數的默認值是TRUE,表示在Oracle 9i及其後續的版本中,默認啟用綁定變數窺探。
SYS@orclasm > set pagesize 9999 SYS@orclasm > set line 9999 SYS@orclasm > col NAME format a40 SYS@orclasm > col KSPPDESC format a50 SYS@orclasm > col KSPPSTVL format a20 SYS@orclasm > SELECT a.INDX, 2 a.KSPPINM NAME, 3 a.KSPPDESC, 4 b.KSPPSTVL 5 FROM x$ksppi a, 6 x$ksppcv b 7 WHERE a.INDX = b.INDX 8 and lower(a.KSPPINM) like lower('%¶meter%'); Enter value for parameter: _OPTIM_PEEK_USER_BINDS old 8: and lower(a.KSPPINM) like lower('%¶meter%') new 8: and lower(a.KSPPINM) like lower('%_OPTIM_PEEK_USER_BINDS%') INDX NAME KSPPDESC KSPPSTVL ---------- ---------------------------------------- -------------------------------------------------- -------------------- 2050 _optim_peek_user_binds enable peeking of user binds TRUE 綁定變數窺探的優缺點如下所示:
① 優點:當綁定變數窺探被啟用後,每當Oracle以硬解析的方式解析使用了綁定變數的目標SQL時,Oracle都會實際窺探(Peeking)一下對應綁定變數的具體輸入值,並以這些具體輸入值為標準,來決定這些使用了綁定變數的目標SQL的WHERE條件的Selectivity和Cardinality的值,並據此來選擇該SQL的執行計劃。需要注意的是,這裡這個「窺探(Peeking)」的動作只有在硬解析的時候才會執行,當使用了綁定變數的目標SQL再次執行時(此時對應的是軟解析/軟軟解析),即便此時對應綁定變數的具體輸入值和之前硬解析時對應的值不同,Oracle也會沿用之前硬解析時所產生的解析樹和執行計劃,而不再重複執行上述「窺探」的動作。因為有了綁定變數窺探,所以,Oracle在計算目標SQL的WHERE條件的Selectivity和Cardinality的值時,就可以避免使用默認的可選擇率,這樣就有更大的可能性能得到該SQL準確的執行計劃。
② 缺點:對於那些執行計劃可能會隨著對應綁定變數具體輸入值的不同而變化的目標SQL而言一旦啟用了綁定變數窺探,其執行計劃就會被固定下來,至於這個固定下來的執行計划到底是什麼,則完全倚賴於該SQL在硬解析時傳入的對應綁定變數的具體值。這意味著一旦啟用了綁定變數窺探,目標SQL在後續執行時就只會沿用之前硬解析所產生的解析樹和執行計劃,即使當時的執行計劃和解析樹並不適合於新傳入的值。
關於綁定變數窺探需要注意以下幾點:
(1)在Oracle llg中引入自適應游標共享後,綁定變數窺探這種不管後續傳入的綁定變數的具體輸入值是什麼而一直沿用之前硬解析時所產生的解析樹和執行計劃的缺點才有所緩解。
(2)在不改變SQL語句文本的情況下,讓SQL語句重新進行硬解析的方法有:①對SQL語句涉及到的對象執行DDL操作(例如COMMENT語句)。②執行DBMS_SHARED_POOL.PURGE來刪除共享池中的游標。③在重新收集統計資訊時指定NO_INVALIDATE=>FALSE選項。
綁定變數窺探這種不管後續傳入的綁定變數的具體輸入值是什麼而一直沿用之前硬解析時所產生的解析樹和執行計劃的特性一直飽受詬病(這種狀況一直到Oracle llg中引入自適應游標共享後才有所緩解),因為綁定變數窺探可能使CBO在某些情況下(對應綁定變數的某些具體輸入值)所選擇的執行計劃並不是目標SQL在當前情形下的最優執行計劃,而且它可能會帶來目標SQL執行計劃的突然改變,進而直接影響應用系統的性能。
綁定變數窺探的副作用在於,一旦啟用(默認情況下綁定變數窺探就己經被啟用),使用了綁定變數的目標SQL就只會沿用之前硬解析時所產生的解析樹和執行計劃,即使這種沿用完全不適合當前的情形,即根據第一次傳入的值然後固化執行計劃。
下面給出綁定變數窺探的示例:
資料庫版本為11.2.0.3,做如下的準備工作:
CREATE TABLE T_BP_20170609_LHR AS SELECT * FROM DBA_OBJECTS; CREATE INDEX IDX_OBJID_LHR ON T_BP_20170609_LHR(OBJECT_ID); SELECT COUNT(1) FROM T_BP_20170609_LHR; EXEC DBMS_STATS.GATHER_TABLE_STATS(USER,'T_BP_20170609_LHR',ESTIMATE_PERCENT => 100,CASCADE => TRUE,METHOD_OPT => 'FOR ALL COLUMNS SIZE 1',NO_INVALIDATE => FALSE);--不收集直方圖 SELECT COUNT(1) FROM T_BP_20170609_LHR T WHERE T.OBJECT_ID BETWEEN 100 AND 102; --3 5q51c7s4z0dp9 SELECT COUNT(1) FROM T_BP_20170609_LHR T WHERE T.OBJECT_ID BETWEEN 100 AND 60000; --59539 gpbkt45132r8x LHR@orclasm > COL SQL_TEXT FORMAT A100 LHR@orclasm > SELECT A.SQL_TEXT, A.SQL_ID,A.VERSION_COUNT FROM V$sqlarea A WHERE A.SQL_TEXT LIKE 'SELECT COUNT(1) FROM T_BP_20170609_LHR T%'; SQL_TEXT SQL_ID VERSION_COUNT ---------------------------------------------------------------------------------------------------- ------------- ------------- SELECT COUNT(1) FROM T_BP_20170609_LHR T WHERE T.OBJECT_ID BETWEEN 100 AND 102 5q51c7s4z0dp9 1 SELECT COUNT(1) FROM T_BP_20170609_LHR T WHERE T.OBJECT_ID BETWEEN 100 AND 60000 gpbkt45132r8x 1 --下面查看其執行計劃: LHR@orclasm > SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR('5q51c7s4z0dp9',0,'advanced')); PLAN_TABLE_OUTPUT -------------------------------------------------- SQL_ID 5q51c7s4z0dp9, child number 0 ------------------------------------- SELECT COUNT(1) FROM T_BP_20170609_LHR T WHERE T.OBJECT_ID BETWEEN 100 AND 102 Plan hash value: 1089369592 ----------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ----------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | | | 2 (100)| | | 1 | SORT AGGREGATE | | 1 | 5 | | | |* 2 | INDEX RANGE SCAN| IDX_OBJID_LHR | 3 | 15 | 2 (0)| 00:00:01 | ----------------------------------------------------------------------------------- Query Block Name / Object Alias (identified by operation id): ------------------------------------------------------------- 1 - SEL$1 2 - SEL$1 / T@SEL$1 Outline Data ------------- /*+ BEGIN_OUTLINE_DATA IGNORE_OPTIM_EMBEDDED_HINTS OPTIMIZER_FEATURES_ENABLE('11.2.0.3') DB_VERSION('11.2.0.3') ALL_ROWS OUTLINE_LEAF(@"SEL$1") INDEX(@"SEL$1" "T"@"SEL$1" ("T_BP_20170609_LHR"."OBJECT_ID")) END_OUTLINE_DATA */ Predicate Information (identified by operation id): --------------------------------------------------- 2 - access("T"."OBJECT_ID">=100 AND "T"."OBJECT_ID"<=102) Column Projection Information (identified by operation id): ----------------------------------------------------------- 1 - (#keys=0) COUNT(*)[22] 45 rows selected. LHR@orclasm > SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR('gpbkt45132r8x',0,'advanced')); PLAN_TABLE_OUTPUT --------------------------------------------- SQL_ID gpbkt45132r8x, child number 0 ------------------------------------- SELECT COUNT(1) FROM T_BP_20170609_LHR T WHERE T.OBJECT_ID BETWEEN 100AND 60000 Plan hash value: 1768048749 --------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | | | 50 (100)| | | 1 | SORT AGGREGATE | | 1 | 5 | | | |* 2 | INDEX FAST FULL SCAN| IDX_OBJID_LHR | 28807 | 140K| 50 (2)| 00:00:01 | --------------------------------------------------------------------------------------- Query Block Name / Object Alias (identified by operation id): ------------------------------------------------------------- 1 - SEL$1 2 - SEL$1 / T@SEL$1 Outline Data ------------- /*+ BEGIN_OUTLINE_DATA IGNORE_OPTIM_EMBEDDED_HINTS OPTIMIZER_FEATURES_ENABLE('11.2.0.3') DB_VERSION('11.2.0.3') ALL_ROWS OUTLINE_LEAF(@"SEL$1") INDEX_FFS(@"SEL$1" "T"@"SEL$1" ("T_BP_20170609_LHR"."OBJECT_ID")) END_OUTLINE_DATA */ Predicate Information (identified by operation id): --------------------------------------------------- 2 - filter(("T"."OBJECT_ID"<=60000 AND "T"."OBJECT_ID">=100)) Column Projection Information (identified by operation id): ----------------------------------------------------------- 1 - (#keys=0) COUNT(*)[22] --可見,SQL1選擇的是索引範圍掃描,SQL2選擇的是索引快速全掃描,下面使用綁定變數的形式,定義兩個綁定變數X和Y,並賦值100和102: LHR@orclasm > VAR X NUMBER; LHR@orclasm > VAR Y NUMBER; LHR@orclasm > EXEC :X :=100; PL/SQL procedure successfully completed. LHR@orclasm > EXEC :Y :=102; PL/SQL procedure successfully completed. LHR@orclasm > SELECT COUNT(1) FROM T_BP_20170609_LHR T WHERE T.OBJECT_ID BETWEEN :X AND :Y; COUNT(1) ---------- 3 LHR@orclasm > SELECT A.SQL_TEXT, A.SQL_ID,A.VERSION_COUNT FROM V$sqlarea A WHERE A.SQL_TEXT LIKE 'SELECT COUNT(1) FROM T_BP_20170609_LHR T%'; SQL_TEXT SQL_ID VERSION_COUNT ---------------------------------------------------------------------------------------------------- ------------- ------------- SELECT COUNT(1) FROM T_BP_20170609_LHR T WHERE T.OBJECT_ID BETWEEN 100 AND 102 5q51c7s4z0dp9 1 SELECT COUNT(1) FROM T_BP_20170609_LHR T WHERE T.OBJECT_ID BETWEEN :X AND :Y gya9jjznchps5 1 SELECT COUNT(1) FROM T_BP_20170609_LHR T WHERE T.OBJECT_ID BETWEEN 100 AND 60000 gpbkt45132r8x 1 LHR@orclasm > SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR('gya9jjznchps5',0,'advanced')); PLAN_TABLE_OUTPUT ------------------------------------------------- SQL_ID gya9jjznchps5, child number 0 ------------------------------------- SELECT COUNT(1) FROM T_BP_20170609_LHR T WHERE T.OBJECT_ID BETWEEN :X AND :Y Plan hash value: 196260839 ------------------------------------------------------------------------------------ | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------------ | 0 | SELECT STATEMENT | | | | 2 (100)| | | 1 | SORT AGGREGATE | | 1 | 5 | | | |* 2 | FILTER | | | | | | |* 3 | INDEX RANGE SCAN| IDX_OBJID_LHR | 3 | 15 | 2 (0)| 00:00:01 | ------------------------------------------------------------------------------------ Query Block Name / Object Alias (identified by operation id): ------------------------------------------------------------- 1 - SEL$1 3 - SEL$1 / T@SEL$1 Outline Data ------------- /*+ BEGIN_OUTLINE_DATA IGNORE_OPTIM_EMBEDDED_HINTS OPTIMIZER_FEATURES_ENABLE('11.2.0.3') DB_VERSION('11.2.0.3') ALL_ROWS OUTLINE_LEAF(@"SEL$1") INDEX(@"SEL$1" "T"@"SEL$1" ("T_BP_20170609_LHR"."OBJECT_ID")) END_OUTLINE_DATA */ Peeked Binds (identified by position): -------------------------------------- 1 - :X (NUMBER): 100 2 - :Y (NUMBER): 102 Predicate Information (identified by operation id): --------------------------------------------------- 2 - filter(:X<=:Y) 3 - access("T"."OBJECT_ID">=:X AND "T"."OBJECT_ID"<=:Y) Column Projection Information (identified by operation id): ----------------------------------------------------------- 1 - (#keys=0) COUNT(*)[22] 從上述顯示內容可以看出,Oracle此時選擇的執行計劃是走對索引IDX_OBJID_LHR的索引範圍掃描,而且Oracle評估出來執行這個索引範圍掃描所返回結果集的Cardinality的值為3。注意到「Peeked Binds」部分的內容為「1 - :X (NUMBER): 100」和「2 - :Y (NUMBER): 102」,這說明Oracle在硬解析上述SQL的過程中確實使用了綁定變數窺探,且做「窺探」這個動作時看到的綁定變數X和Y的具體輸入值分別為100和102。
現在保持X的值不變,將Y的值修改為60000,如下所示:
LHR@orclasm > EXEC :Y :=60000; PL/SQL procedure successfully completed. LHR@orclasm > SELECT COUNT(1) FROM T_BP_20170609_LHR T WHERE T.OBJECT_ID BETWEEN :X AND :Y; COUNT(1) ---------- 59539 LHR@orclasm > SELECT A.SQL_TEXT, A.SQL_ID,A.VERSION_COUNT,A.EXECUTIONS FROM V$SQLAREA A WHERE A.SQL_TEXT LIKE 'SELECT COUNT(1) FROM T_BP_20170609_LHR T%'; SQL_TEXT SQL_ID VERSION_COUNT EXECUTIONS ---------------------------------------------------------------------------------------------------- ------------- ------------- ---------- SELECT COUNT(1) FROM T_BP_20170609_LHR T WHERE T.OBJECT_ID BETWEEN 100 AND 102 5q51c7s4z0dp9 1 1 SELECT COUNT(1) FROM T_BP_20170609_LHR T WHERE T.OBJECT_ID BETWEEN :X AND :Y gya9jjznchps5 1 2 SELECT COUNT(1) FROM T_BP_20170609_LHR T WHERE T.OBJECT_ID BETWEEN 100 AND 60000 gpbkt45132r8x 1 1
從查詢結果可以看到上述SQL對應的列VERSION_COUNT的值為1,列EXECUTIONS的值為2,這說明Oracle在第二次執行該SQL時用的是軟解析。此時SQL的執行計劃為:
LHR@orclasm > SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR('gya9jjznchps5',0,'advanced')); PLAN_TABLE_OUTPUT ------------------------------------------------- SQL_ID gya9jjznchps5, child number 0 ------------------------------------- SELECT COUNT(1) FROM T_BP_20170609_LHR T WHERE T.OBJECT_ID BETWEEN :X AND :Y Plan hash value: 196260839 ------------------------------------------------------------------------------------ | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------------ | 0 | SELECT STATEMENT | | | | 2 (100)| | | 1 | SORT AGGREGATE | | 1 | 5 | | | |* 2 | FILTER | | | | | | |* 3 | INDEX RANGE SCAN| IDX_OBJID_LHR | 3 | 15 | 2 (0)| 00:00:01 | ------------------------------------------------------------------------------------ Query Block Name / Object Alias (identified by operation id): ------------------------------------------------------------- 1 - SEL$1 3 - SEL$1 / T@SEL$1 Outline Data ------------- /*+ BEGIN_OUTLINE_DATA IGNORE_OPTIM_EMBEDDED_HINTS OPTIMIZER_FEATURES_ENABLE('11.2.0.3') DB_VERSION('11.2.0.3') ALL_ROWS OUTLINE_LEAF(@"SEL$1") INDEX(@"SEL$1" "T"@"SEL$1" ("T_BP_20170609_LHR"."OBJECT_ID")) END_OUTLINE_DATA */ Peeked Binds (identified by position): -------------------------------------- 1 - :X (NUMBER): 100 2 - :Y (NUMBER): 102 Predicate Information (identified by operation id): --------------------------------------------------- 2 - filter(:X<=:Y) 3 - access("T"."OBJECT_ID">=:X AND "T"."OBJECT_ID"<=:Y) Column Projection Information (identified by operation id): ----------------------------------------------------------- 1 - (#keys=0) COUNT(*)[22] 從上述顯示內容可以看出,Oracle此時依然選擇的執行計劃是走對索引IDX_OBJID_LHR的索引範圍掃描,而且「Peeked Binds」部分的內容依然為「1 - :X (NUMBER): 100」和「2 - :Y (NUMBER): 102」,這說明Oracle在第2次執行使用綁定變數的SQL時不再重複做「窺探」動作。
此時若想Oracle進行「窺探」動作,則只能讓以前的共享游標失效,重複執行一次硬解析即可。讓Oracle再次執行目標SQL時使用硬解析的方法有很多,其中很常見的一種方法是對目標SQL中所涉及的表執行DDL操作。因為一旦對某個表執行了DDL操作,庫快取中所有在SQL文本中包含了這個表的Shared Cursor都會被Oracle標記為失效(INVALID),這意味著這些Shared Cursor中存儲的解析樹和執行計劃將不再能被重用,所以當Oracle再次執行與這個表相關的SQL時就會使用硬解析。
所以,現在只需要對錶T_BP_20170609_LHR執行一個DDL操作,就可以讓Oracle再次執行上述SQL時使用硬解析了。DDL操作有很多種,通常選擇添加註釋的COMMENT語句,因為使用COMMENT添加註釋也是DDL操作,但同時它的殺傷力和對生產環境的影響又微乎其微。
LHR@orclasm > COMMENT ON TABLE T_BP_20170609_LHR IS 'TEST BIND PEEKING'; Comment created. LHR@orclasm > SELECT COUNT(1) FROM T_BP_20170609_LHR T WHERE T.OBJECT_ID BETWEEN :X AND :Y; COUNT(1) ---------- 59539 LHR@orclasm > SELECT A.SQL_TEXT, A.SQL_ID,A.VERSION_COUNT,A.EXECUTIONS FROM V$SQLAREA A WHERE A.SQL_TEXT LIKE 'SELECT COUNT(1) FROM T_BP_20170609_LHR T%'; SQL_TEXT SQL_ID VERSION_COUNT EXECUTIONS ---------------------------------------------------------------------------------------------------- ------------- ------------- ---------- SELECT COUNT(1) FROM T_BP_20170609_LHR T WHERE T.OBJECT_ID BETWEEN 100 AND 102 5q51c7s4z0dp9 1 1 SELECT COUNT(1) FROM T_BP_20170609_LHR T WHERE T.OBJECT_ID BETWEEN :X AND :Y gya9jjznchps5 1 1 SELECT COUNT(1) FROM T_BP_20170609_LHR T WHERE T.OBJECT_ID BETWEEN 100 AND 60000 gpbkt45132r8x 1 1
從查詢結果可以看到上述SQL對應的列VERSION_COUNT的值為1,列EXECUTIONS的值由之前的2變為了現在的1,這說明Oracle在第三次執行上述SQL時用的是硬解析(EXECUTIONS的值為1,是因為Oracle在這裡重新生成了一對Parent Cursor和Child Cursor,原先EXECUTIONS的值為2所對應的Shared Cursor己經被Oracle標記為INVALID,相當於被廢棄了)。
LHR@orclasm > SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR('gya9jjznchps5',0,'advanced')); PLAN_TABLE_OUTPUT ----------------------------------------------------------------------------- SQL_ID gya9jjznchps5, child number 0 ------------------------------------- SELECT COUNT(1) FROM T_BP_20170609_LHR T WHERE T.OBJECT_ID BETWEEN :X AND :Y Plan hash value: 2066501558 ---------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ---------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | | | 50 (100)| | | 1 | SORT AGGREGATE | | 1 | 5 | | | |* 2 | FILTER | | | | | | |* 3 | INDEX FAST FULL SCAN| IDX_OBJID_LHR | 28807 | 140K| 50 (2)| 00:00:01 | ---------------------------------------------------------------------------------------- Query Block Name / Object Alias (identified by operation id): ------------------------------------------------------------- 1 - SEL$1 3 - SEL$1 / T@SEL$1 Outline Data ------------- /*+ BEGIN_OUTLINE_DATA IGNORE_OPTIM_EMBEDDED_HINTS OPTIMIZER_FEATURES_ENABLE('11.2.0.3') DB_VERSION('11.2.0.3') ALL_ROWS OUTLINE_LEAF(@"SEL$1") INDEX_FFS(@"SEL$1" "T"@"SEL$1" ("T_BP_20170609_LHR"."OBJECT_ID")) END_OUTLINE_DATA */ Peeked Binds (identified by position): -------------------------------------- 1 - :X (NUMBER): 100 2 - :Y (NUMBER): 60000 Predicate Information (identified by operation id): --------------------------------------------------- 2 - filter(:X<=:Y) 3 - filter(("T"."OBJECT_ID"<=:Y AND "T"."OBJECT_ID">=:X)) Column Projection Information (identified by operation id): ----------------------------------------------------------- 1 - (#keys=0) COUNT(*)[22] 53 rows selected. LHR@orclasm > 從上述顯示內容可以看出,Oracle此時選擇的執行計劃己經從之前的走對索引IDX_OBJID_LHR的索引範圍掃描變為索引快速全掃描,而且Oracle評估出來執行這個索引快速全掃描所返回結果集的Cardinality的值為28807。注意到「Peeked Binds」部分的內容為「1 - :X (NUMBER): 100」和「2 - :Y (NUMBER): 60000」,這說明Oracle在執行上述SQL的過程中確實又一次使用了綁定變數窺探,且做「窺探」這個動作時看到的綁定變數和X和Y的具體輸入值分別為100和60000。
在這裡,Oracle評估出來對索引IDX_OBJID_LHR執行索引範圍掃描所返回結果集的Cardinality的值為3,對執行索引快速全掃描所返回結果集的Cardinality的值為28807。下面來解釋一下這兩個值是如何計算出來的:
參考下一篇。
本文選自《Oracle程式設計師面試筆試寶典》,作者:李華榮。