1. 程式人生 > >Oracle SQL調優之繫結變數用法簡介

Oracle SQL調優之繫結變數用法簡介

最近在看《基於Oracle的SQL優化一書》,並做了筆記,作者的個人部落格:[http://www.dbsnake.net/](http://www.dbsnake.net/) @[toc] ## 一、SQL執行過程簡介 繼上一篇部落格Oracle的cursor學習筆記:[Oracle的遊標Cursor原理簡介](https://smilenicky.blog.csdn.net/article/details/99946785),再介紹oracle的繫結變數 介紹繫結變數之前,先介紹SQL執行過程和硬解析的概念: 執行sql的過程,會將sql的文字進行hash運算,得到物件的hash值,然後拿hash值,去Hash Buckets裡遍歷快取物件控制代碼連結串列,找到對應的快取物件控制代碼,然後就可以得到快取物件控制代碼裡對應sql執行計劃、解析樹等物件,所以執行相同的sql第二次執行時是會比較快的,因為不需要解析獲取執行計劃,解析樹等物件,如果找不到庫快取物件控制代碼,就需要重新解析,這個過程解析過多,容易造成硬解析問題 > 硬解析:是指Oracle在執行目標SQL時,在庫快取中找不到可以重用的解析樹和執行計劃,而不得不從頭開始解析目標SQL並生成相應的Parent Cursor和Child Cursor的過程。
> 軟解析:是指Oracle在執行目標SQL時,在Library Cache中找到了匹配的Parent Cursor和Child Cursor,並將儲存在Child Cursor中的解析樹和執行計劃直接拿過來重用,無須從頭開始解析的過程。 ok,上面是SQL執行過程的簡單介紹,由此可知,假如sql執行過程,在共享池裡找不到執行計劃、解析樹等就會重現解析sql,生成執行計劃和解析樹等,這個過程是比較耗時間的,所以要想辦法儘量不要重現解析sql,需要執行計劃直接去共享池拿已經生成的 舉個例子,`select * from sys_user where userid='u10001';`和`select * from sys_user where userid='u10002';`,這兩個很類似的sql在執行過程,生成的執行計劃很有可能是不一樣的,也就是說第一條sql執行後,第二條sql繼續執行,假如發現找不到對應執行計劃,就會再解析sql,重現生成session cursor和一對shared cursor(parent cursor和child cursor) 然後,我們不想重新解析sql,有什麼方法?方法就是用繫結變數的方法 ## 二、繫結變數典型用法 ### 2.1、在SQL中繫結變數 繫結變數的典型用法就是用 :variable_name的形式,variable_name是自定義的變數名稱,variabl_name可以是字母、數字或者字母和數字的組合 ok,上面的那種型別的sql,就可以用一條帶繫結變數的sql來表示: ``` select * from sys_user where userid = :u; ``` 這樣這種型別的一堆sql都只會解析一次,不用每條sql都解析一遍,可以很好的提高系統處理能力 ok,舉個例子說明 環境準備: ``` /* 隨便建一張表*/ create table t as select * from dba_objects; ``` 注意,這些指令碼只能在sqlplus或者PLSQL客戶端的命令視窗執行 ``` /* 定義繫結變數vid */ SQL> variable vid number; /* 給繫結變數賦值為2 */ SQL> exec :vid := 2; ``` 在sqlplus或者PLSQL客戶端的命令視窗執行 ``` /* 通過繫結變數查詢 */ SQL> select * from t where object_id = :vid; ``` ``` /*通過效能檢視查詢SQL解析情況*/ select a.*, b.name from v$sesstat a, v$statname b where a.statistic# = b.statistic# and a.sid = (select distinct sid from v$mystat) and b.name like '%parse%'; ``` ``` /* 去共享池查詢一下這種型別的SQL資訊*/ select sql_text, parse_calls, executions from v$sql where sql_text like 'select * from t where object_id=%'; ``` ``` /* 通過共享池查詢查詢最慢的10條sql*/ SELECT * FROM (select PARSING_USER_ID, EXECUTIONS, SORTS, COMMAND_TYPE, DISK_READS, sql_text FROM v$sqlarea order BY disk_reads DESC) where ROWNUM < 10; ``` ### 2.2、在PL/SQL中使用繫結變數 ``` /* SQL語句使用繫結變數*/ declare vc_empname varchar2(10); begin execute immediate 'select ename from t_emp where empno = :1' into vc_empname using 7369; dbms_output.put_line(vc_empname); end; / ``` ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20190919193400518.png) 往t_emp表寫入一條資料,並統計是否執行成功,返回數值 ``` /*DML語句使用繫結變數*/ declare vc_sql varchar2(2000); vc_number number; begin vc_sql := 'insert into t_emp(empno,ename,job) values(:1,:2,:3)'; execute immediate vc_sql using 7990,'SMITH','HR'; vc_number := sql%rowcount; dbms_output.put_line(to_char(vc_number)); commit; end; / ``` 所以繫結變數在pl/sql裡的核心語法為: ``` execute immediate [sql語句] using [變數] ``` ### 2.3、PL/SQL批量繫結變數 例子來自《基於Oracle的SQL優化》一書,要實現的的是批量繫結變數,fetch關鍵字,將empno大於7900的職員資訊打印出來 ``` declare cur_emp sys_refcursor; vc_sql varchar2(2000); type namelist is table of varchar2(10); enames namelist; CN_BATCH_SIZE constant pls_integer := 1000; begin vc_sql:= 'select ename from t_emp where empno > :1'; open cur_emp for vc_sql using 7900; loop fetch cur_emp bulk collect into enames limit CN_BATCH_SIZE; for i in 1..enames.count loop dbms_output.put_line(enames(i)); end loop; exit when enames.count < CN_BATCH_SIZE; end loop; close cur_emp; end; / ``` ### 2.4、Java程式碼裡使用繫結變數 不用繫結變數的寫法: ``` String empno = '7369'; String query_sql = 'select ename from t_emp where empno = 7369 '; stmt = con.prepareStatement( query_sql ); stmt.executeQuery(); ``` 使用繫結變數的寫法: ``` String empno = 'xxxxx'; String query_sql = 'select ename from t_emp where empno = ? '; //嵌入繫結變數 stmt = con.prepareStatement( query_sql ); stmt.setString(1, empno ); //為繫結變數賦值 stmt.executeQuery(); ``` 批量繫結變數寫法: 此例子來自《基於Oracle的SQL優化》一書: ``` String vc_sql = 'update t_emp set sal = ? where empno = ?'; pstmt = connection.prepareStatement(dml); pstmt.clearBatch(); for (int i = 0; i < UPDATE_COUNT; ++ i) { pstmt.setInt(1, generateEmpno(i)); pstms.setInt(2, generateSal(i)); pstmt.addBatch(); } pstmt.executeBatch(); connection.commi