請關注個人小站:http://sqlhis.com/
在Oracle數據庫中執行SQL語句,當客戶端發出一條語句交付到ORACLE,會進行以下幾個步驟:
1、語法檢查(syntax check)
檢查此sql的拼寫是否語法。
2、語義檢查(semantic check)
諸如檢查sql語句中的訪問對象是否存在及該用戶是否具備相應的權限。
3、對sql語句進行解析(prase)
利用內部算法對sql進行解析,生成解析樹(parse tree)及執行計划(execution plan)。
4、執行sql,返回結果(execute and return)
其中,軟、硬解析就發生在第三個過程里。
如果使用了綁定變量,且綁定變量類型一直未變話,則通常只在第一次執行的時候進行一次硬解析(優化器創建解析樹、生成執行計划),后續都是軟解析(將此SQL和cache中的進行比較,如果相同,取已生成的執行計划),創建解析樹、生成執行計划對於sql的執行來說是開銷昂貴的動作,所以,應當極力避免硬解析,盡量使用軟解析。
本范例想說明:即便SQL語句完全相同,但是如果綁定變量的類型或者長度發生了變化的話,也會發生硬解析.
建立測試表並清理緩存
--建立測試表 create table TESTBIND ( aaa CHAR(10), bbb CHAR(100), ccc CHAR(2000) ) --清理緩存 ALTER SYSTEM FLUSH SHARED_POOL; alter system flush BUFFER_CACHE;
執行一次插入操作
--第一次綁定 DECLARE v_AAA CHAR(10):='A'; v_BBB CHAR(10):='B'; v_CCC CHAR(10):='C'; BEGIN INSERT INTO TESTBIND VALUES(v_AAA,v_BBB,v_CCC); COMMIT; END;
查找被緩存的執行計划
SELECT SQL_TEXT,SQL_ID,LOADED_VERSIONS,OPEN_VERSIONS,EXECUTIONS, CHILD_NUMBER FROM V$sql WHERE sql_text LIKE '%INSERT INTO TESTBIND%' AND sql_TEXT NOT LIKE '%DECLARE%'
返回結果如圖:
SQL_ID:這段SQL的唯一ID
EXECUTIONS:這段語句執行次數
多次執行上面的插入語句並檢查V$SQL緩存,發現EXECUTIONS不斷增加,這說明后續都是執行的軟解析,即復用了第一次生成的執行計划
再次執行插入操作,這次改變綁定變量的類型,v_AAA的類型從CHAR(10) 改為了VARCHAR(10)
--第二次綁定 DECLARE v_AAA VARCHAR(10):='A'; v_BBB CHAR(10):='B'; v_CCC CHAR(10):='C'; BEGIN INSERT INTO TESTBIND VALUES(v_AAA,v_BBB,v_CCC); COMMIT; END;
然后再次查看V$SQL,發現現在有兩條記錄,也就是說,同一個語句,由於傳入的綁定變量類型不同,在數據庫中有兩個執行計划,執行計划可以通過CHILD_NUMBER來區分
SELECT SQL_TEXT,SQL_ID,LOADED_VERSIONS,OPEN_VERSIONS,EXECUTIONS, CHILD_NUMBER FROM V$sql WHERE sql_text LIKE '%INSERT INTO TESTBIND%' AND sql_TEXT NOT LIKE '%DECLARE%'
為了看的更清楚一點,我們觀察v$sql_shared_cursor,這個視圖會說明兩個執行計划不一致的原因,v$sql_shared_cursor有很多不同的列,標識了各種執行計划不能復用的原因,由於我們今天只測試改變綁定變量類型和長度,所以只需要關注:
BIND_MISMATCH:當為Y的時候表示綁定變量類型不一致
BIND_LENGTH_UPGRADEABLE:當為Y的時候表示綁定變量類型的長度發生了變化
REASON:一個XML輸出,網上搜索了一遍,也沒看到這個XML的解釋,總之比較奇怪,不太看的明白
SELECT SQL_ID,CHILD_NUMBER,BIND_MISMATCH,BIND_LENGTH_UPGRADEABLE,REASON FROM v$sql_shared_cursor
WHERE sql_id IN (SELECT sql_ID FROM V$sql WHERE sql_text LIKE '%INSERT INTO TESTBIND%' AND sql_TEXT NOT LIKE '%DECLARE%')
查詢結果如下:BIND_MISMATCH=Y,表示綁定變量的類型發生了變化
REASON字段的XML,除了標紅的Bind mismatch(8)可以理解為綁定變量不一致外,其他都不太理解,且找了一輪沒有注釋
<ChildNode> <ChildNumber>0</ChildNumber> <ID>40</ID> <reason>Bind mismatch(8)</reason> <size>4x4</size> <bind_position>0</bind_position> <original_oacflg>19</original_oacflg> <original_oacdty>96</original_oacdty> <new_oacdty>1</new_oacdty> </ChildNode>
從視圖可以查看每個查詢計划綁定變量的類型
SELECT * FROM v$sql_bind_capture WHERE sql_id IN (SELECT sql_ID FROM V$sql WHERE sql_text LIKE '%INSERT INTO TESTBIND%' AND sql_TEXT NOT LIKE '%DECLARE%')
根據CHILD_NUMBER區分,可以看出參數B3 的類型發生了變化,注意這里類型的長度都是32,后面會說到原因
查詢具體的執行計划可以通過以下語句,第一個參數是SQL_ID,第二個參數是CHILD_NUMBER
select * from table(dbms_xplan.display_cursor('4yddzp87tmzza',0)); select * from table(dbms_xplan.display_cursor('4yddzp87tmzza',1));
再次執行插入語句,這次將V_AAA類型的長度從10改為33
--第三次綁定 DECLARE v_AAA VARCHAR(33):='A'; v_BBB CHAR(10):='B'; v_CCC CHAR(10):='C'; BEGIN INSERT INTO TESTBIND VALUES(v_AAA,v_BBB,v_CCC); COMMIT; END;
再次通過語句檢查執行計划和綁定變量類型
SELECT SQL_TEXT,SQL_ID,LOADED_VERSIONS,OPEN_VERSIONS,EXECUTIONS, CHILD_NUMBER FROM V$sql WHERE sql_text LIKE '%INSERT INTO TESTBIND%' AND sql_TEXT NOT LIKE '%DECLARE%' SELECT SQL_ID,CHILD_NUMBER,BIND_MISMATCH,BIND_LENGTH_UPGRADEABLE,REASON FROM v$sql_shared_cursor WHERE sql_id IN (SELECT sql_ID FROM V$sql WHERE sql_text LIKE '%INSERT INTO TESTBIND%' AND sql_TEXT NOT LIKE '%DECLARE%') SELECT * FROM v$sql_bind_capture WHERE sql_id IN (SELECT sql_ID FROM V$sql WHERE sql_text LIKE '%INSERT INTO TESTBIND%' AND sql_TEXT NOT LIKE '%DECLARE%')
V$SQL視圖,可見又多了一個執行計划
v$sql_shared_cursor視圖,可見BIND_LENGTH_UPGRADEABLE=Y,即表示重新編譯原因是由於綁定變量的長度發生變化引起
v$sql_bind_capture視圖,可見數據長度從32變為128
再次執行一個插入語句
--第四次綁定 DECLARE v_AAA VARCHAR(129):='A'; v_BBB CHAR(10):='B'; v_CCC CHAR(10):='C'; BEGIN INSERT INTO TESTBIND VALUES(v_AAA,v_BBB,v_CCC); COMMIT; END;
可見一一共4個執行計划,每個插入都是不同的執行計划
解釋了每次重新編譯的原因
每次綁定變量的差異,其中關注一下類型的長度分別是32,128,2000,也就是根據傳入類型的長度進行了區間划分
1-32分配到32
33到128分配到128
129到2000分配到2000,
這樣的話,不會每改變一次傳入變量長度執行計划就編譯一次
可以試下將綁定變量V_AAA長度設置為1000
--第五次綁定 DECLARE v_AAA VARCHAR(1000):='A'; v_BBB CHAR(10):='B'; v_CCC CHAR(10):='C'; BEGIN INSERT INTO TESTBIND VALUES(v_AAA,v_BBB,v_CCC); COMMIT; END;
無論執行多少次,都不會有新的執行計划產生,實際上使用了VARCHAR2(2000)這個參數相關的執行計划
最后匯總以下查詢語句:
--檢查緩存 SELECT SQL_TEXT,SQL_ID,LOADED_VERSIONS,OPEN_VERSIONS,EXECUTIONS, CHILD_NUMBER FROM V$sql WHERE sql_text LIKE '%INSERT INTO TESTBIND%' AND sql_TEXT NOT LIKE '%DECLARE%' --v$SQL的按SQL_ID的匯總表 SELECT * FROM v$sqlarea WHERE sql_id IN (SELECT sql_ID FROM V$sql WHERE sql_text LIKE '%INSERT INTO TESTBIND%' AND sql_TEXT NOT LIKE '%DECLARE%') --相同語句使用不同執行計划的具體原因 SELECT SQL_ID,CHILD_NUMBER,BIND_MISMATCH,BIND_LENGTH_UPGRADEABLE,REASON FROM v$sql_shared_cursor WHERE sql_id IN (SELECT sql_ID FROM V$sql WHERE sql_text LIKE '%INSERT INTO TESTBIND%' AND sql_TEXT NOT LIKE '%DECLARE%') --不同執行計划對應的具體綁定變量 SELECT * FROM v$sql_bind_capture WHERE sql_id IN (SELECT sql_ID FROM V$sql WHERE sql_text LIKE '%INSERT INTO TESTBIND%' AND sql_TEXT NOT LIKE '%DECLARE%') --查詢具體的執行計划,第一個參數是SQL_ID,第二個參數和是CHILD_NUMBER select * from table(dbms_xplan.display_cursor('4yddzp87tmzza',0)); select * from table(dbms_xplan.display_cursor('4yddzp87tmzza'