閱讀335 返回首頁    go 阿裏雲 go 技術社區[雲棲]


ORACLE--預編譯與共享池--SGA基本介紹

我們暫時先不說其他的,我們先做一個簡單的實驗來證明來看出一些問題,最後通過為什麼來說明實驗的結論,並介紹原理和常規查看方式,好了,我們先看看下麵三段代碼分別執行的結果。

首先為了測試,我們需要創建一張表:

CREATE TABLE PRE_TEST_TABLE(
   C1  NUMBER,
   C2  VARCHAR2(100)
);

好了,我們做一個插入操作的對比:

代碼段1

BEGIN

      FOR I IN 1..20000 LOOP

           EXECUTE IMMEDIATE 'INSERT INTO PRE_TEST_TABLE VALUES('||i||',''test'')';

      END LOOP;

      COMMIT;

END;

 

代碼片段2

BEGIN

      FOR I IN 1..20000 LOOP

           EXECUTE IMMEDIATE 'INSERT INTO PRE_TEST_TABLE VALUES(:V1,:V2)' USING I,'test';

      END LOOP;

      COMMIT;

END;

 

代碼片段3

BEGIN

      FOR I IN 1..20000 LOOP

           INSERT INTO PRE_TEST_TABLE VALUES(I,'TEST');

      END LOOP;

      COMMIT;

END;

 

三段代碼執行效率你可以清晰的對比出來,代碼段1是最慢的,而且比後兩者慢很多倍,而代碼片段2和代碼片段3執行效率基本是一樣的,為什麼會有這樣的效果呢?看了下麵的推敲,我們就清楚了,我們先把數據清理掉,共享池清理一下(下麵我們再說),在做操作比較好。 

 

現在我需要做的SQL語句操作是對表的插入PRE_TEST_TABLE VALUES表的操作:

INSERT INTO PRE_TEST_TABLE VALUES....

 

至於參數如何,我們不一定,首先查詢一下共享池內部做此操作的記錄:

SELECT * FROM V$SQLAREA
WHERE SQL_TEXT LIKE 'INSERT INTO PRE_TEST_TABLE VALUES%';

 

發現數據太多,有多少不好說了,暫時不數了,因為對得出結論沒有意義,需要清理下共享池方便試驗。

 

TRUNCATE TABLE PRE_TEST_TABLE;//清空表

 

ALTER SYSTEM FLUSH SHARED_POOL;//清空緩衝區

 

查詢共享池:

SELECT * FROM V$SQLAREA
WHERE SQL_TEXT LIKE 'INSERT INTO PRE_TEST_TABLE VALUES%';

發現沒有任何數據。。。。我們開始比較幹淨的做實驗了。。。

 

開始試驗:

我們將上述試驗的循環次數降低為3

首先執行代碼段1

BEGIN

      FOR I IN 1..3 LOOP

           EXECUTE IMMEDIATE 'INSERT INTO PRE_TEST_TABLE VALUES('||i||',''test'')';

      END LOOP;

      COMMIT;

END;

 

查詢共享池(發現多了3條記錄):

SQL> SELECT SQL_TEXT,HASH_VALUE,PARSE_CALLS FROM V$SQLAREA
  2  WHERE SQL_TEXT LIKE 'INSERT INTO PRE_TEST_TABLE VALUES%';

SQL_TEXT                                                            HASH_VALUE      PARSE_CALLS
--------------------------------------------------------------- -------------- ---------------------
INSERT INTO PRE_TEST_TABLE VALUES(1,'test')        2868385396       1
INSERT INTO PRE_TEST_TABLE VALUES(2,'test')        2513099158       1
INSERT INTO PRE_TEST_TABLE VALUES(3,'test')        455953479         1

 

再執行第二段代碼:

BEGIN

      FOR I IN 1..3 LOOP

           EXECUTE IMMEDIATE 'INSERT INTO PRE_TEST_TABLE VALUES(:V1,:V2)' USING I,'test';

      END LOOP;

      COMMIT;

END;

 

再查詢共享池(發現隻多了一條SQL):

SQL> SELECT SQL_TEXT,HASH_VALUE,PARSE_CALLS FROM V$SQLAREA
  2  WHERE SQL_TEXT LIKE 'INSERT INTO PRE_TEST_TABLE VALUES%';

SQL_TEXT                                                              HASH_VALUE  PARSE_CALLS
--------------------------------------------------------------- ---------------- ------------
INSERT INTO PRE_TEST_TABLE VALUES(1,'test')         2868385396    1
INSERT INTO PRE_TEST_TABLE VALUES(:V1,:V2)        357326048      3
INSERT INTO PRE_TEST_TABLE VALUES(2,'test')         2513099158    1
INSERT INTO PRE_TEST_TABLE VALUES(3,'test')         455953479      1

 

再執行第三段代碼:

BEGIN

      FOR I IN 1..3 LOOP

           INSERT INTO PRE_TEST_TABLE VALUES(I,'TEST');

      END LOOP;

      COMMIT;

END;

 

再查詢共享池(發現也隻多了一條SQL):

SQL> SELECT SQL_TEXT,HASH_VALUE,PARSE_CALLS FROM V$SQLAREA
  2  WHERE SQL_TEXT LIKE 'INSERT INTO PRE_TEST_TABLE VALUES%';

SQL_TEXT                                                             HASH_VALUE   PARSE_CALLS
------------------------------------------------------------- ------------------  ------------
INSERT INTO PRE_TEST_TABLE VALUES(1,'test')          2868385396    1
INSERT INTO PRE_TEST_TABLE VALUES(:B1 ,'TEST')     2239119514    3
INSERT INTO PRE_TEST_TABLE VALUES(:V1,:V2)         357326048      3
INSERT INTO PRE_TEST_TABLE VALUES(2,'test')          2513099158    1
INSERT INTO PRE_TEST_TABLE VALUES(3,'test')          455953479      1

 

從這裏可能大家基本可以得出初步的結論,就是第一段代碼每條SQL都會占用共享池,並征用共享池,而且用過一次後就不會在使用了,這個答案是正確的,我首先給出預編譯和拚SQLORACLE的四個不同影響進行定義,下麵再說明為什麼:

1、拚SQL會導致共享池的長期征用狀態

2、拚SQL會導致共享池中存在一堆的垃圾SQL

3、拚SQL會硬解析(Hard parse),而預編譯可以充分利用軟解析。

4、拚SQL會導致注入型錯誤。

 

疑問:

為什麼第二段代碼和第三段代碼是一個效果呢,這裏說明一下,在存儲過程提高效率的基本因素就是你如果按照過程化去寫SQL,存儲過程默認就會按照預編譯方式去執行,因為這是ORACLE優化的基本原則,而即使在過程中使用EXECUTE IMMEDIATE SQL_STR去執行,請注意,EXECUTE IMMEDIATE不是SQL語句,而是ORACLE在過程中支持的命令,即直接發送執行命令給ORACLE的分析器,所以這個是否進行編譯完全看你的SQL是什麼樣的了,而不是ORACLE自己能決定的。

 

過程說明:

SQL從終端通過1521 TCP服務端口以字符串方式傳送至ORACLE後(包含JAVA程序也是這樣,其他應用程序,如出現String sql = "SELECT * FROM A WHERE ID="+id;也會出現和代碼段1一樣的結果),ORACLE通過HASH算法對其SQL轉換,並在共享池中查找是否存在同樣HASH值的SQL(即:SQL即使是參數或者大小寫不同,也會導致找不到一樣的HASH值),如果找到了,直接執行已經編譯完的SQL,並修改使用率(這個有用);若沒有,則首先通過硬解析工具對其進行各項語法分析和性能指標分析等等,然後開始征用共享池(此為共享資源),並注冊到共享池中,標誌調用次數為1,然後再執行,當在大量征用共享資源時候,並且在硬解析過程中,高並發將導致阻塞。可以將上述第一段代碼的循環次數增加為10萬,基本機器可以弄死,嗬嗬。。。。

 

共享池的大小肯定是有限製的,所以ORACLE在共享池不夠用的時候,采用基於LRU為核心的算法進行替換(上述的PARSE_CALLS字段可以基本看出SQL被調用的次數,但是不完全依賴於它),我們最希望的就是不要做這樣的操作,因為這樣的操作必然麵臨磁盤讀取,在內存中獲取我們稱為命中,命中率高才能提高利用率,係統的整體性能才能得到保證。

 

第四點中提到的至於注入型錯誤或攻擊就是傳送特殊字符串,導致SQL執行SQL的修改,為什麼,因為你的SQL是拚出來的,我舉個簡單例子:

你的程序中:

String sql = "SELECT * FROM A WHERE A.NAME = '"+name+"'";

name假如為查詢條件傳入,那麼此時我在查詢條件文本框中這樣輸入:

第一種輸入:

' OR 1=1--

此時你的SQL變成:

SELECT * FROM A WHERE A.NAME ='' OR 1=1 --'

後麵兩杠是屏蔽你後麵的SQL,用一個OR 1=1前麵的東東不論是啥就永遠成立了(注意:ORACLE的SQL執行,優先級是AND 大於 OR的,所以隻要OR 1=1,無論有多少個AND,最後會和這個OR 1=1去匹配,所有的數據都會提取出來),此時分頁或者說導出控製天數或者數據量,都控製不了,有多少數據就會出來多少數據,首先數據權限沒有了,然後開幾個瀏覽器係統就能掛掉,嗬嗬!

 

另一種更加損的輸入招數是:

';DROP TABLE DUAL--

在我以前用SQL SERVERJDBC時,這種方式是支持的,這樣是很損的一種辦法。其實輸入的方式有些通過URL有些通過條件,不一定,而且千奇百怪,有些想都想不到,至少他可能會導致你的SQL執行不是那麼順利篡改了SQL執行的原有意義。

 

所以OLTP係統使用這樣的SQL,尤其對於ORACLE數據庫(其餘數據庫請自己研究下),是非常惡心的,所以我們在OLTP係統是封殺這樣的SQL存在。

 

 

簡要共享池的使用介紹:

談到共享池我們大致介紹一些SGA的內容,ORACLE數據庫我們操作主要對象是實例,而非數據庫本身,主要原因為:性能、安全性。而實例大致分:SGAPGA,本文隻是由共享池介紹一下SGA,細節說下其共享池部分,而PGA後續討論。

 

SGA內部主要包含:數據緩衝區、共享池、JAVA池、大池、Stream池、重做日誌緩衝區

 

PGA內部包含:用戶Session信息、排序信息、Hash area、堆棧。這些信息被後台進程所控製,版本遞增的後台進程也在不斷增加,細節的信息後續討論。

 

其實今天所謂查詢共享池,也是查詢共享池內部的Library cache。我們在SQLPLUS中最常用的命令就是:

SQL> show sga;

Total System Global Area 1.7062E+10 bytes
Fixed Size                      2102776 bytes
Variable Size                  4613736968 bytes
Database Buffers            1.2432E+10 bytes
Redo Buffers                  14671872 bytes

也可以使用一下方式查詢和上述一樣的SGA信息:

 

 

SQL> SELECT * FROM V$SGA;

NAME                      VALUE
-------------------- ----------
Fixed Size               2102776
Variable Size           4613736968
Database Buffers     1.2432E+10
Redo Buffers           14671872

 

分別解釋下幾個字段的意義:

Total System Global Area:代表SGA的總體大小,包含下麵幾者之和,都是以byte為單位,即字節;

Fixed Size:字典信息、控製信息、狀態信息。

Variable Size共享池(shared pool)、Java(Java Pool)、大池(Large Pool)Stream pool

Database Buffers:為數據緩衝區,OLTP係統要求這塊設置較大。

Redo Buffer:重做日誌緩衝區,適當提高緩衝區,減少文件組切換,可以提高效率。

 

通過一下SQL可以得到SGA內部詳細的組件分配情況:

 

SQL> SELECT * FROM V$SGASTAT;

POOL         NAME                            BYTES
------------ -------------------------- ----------
                  fixed_sga                     2102776
                  buffer_cache               1.2432E+10
                  log_buffer                   14671872
shared pool  transaction                   8062512
shared pool  table definiti                  80336
shared pool  KGSKI scheduler heap 2 de         232
shared pool  kspd run-time context              16
shared pool  PX subheap                      61344
shared pool  partitioning d                 455480
shared pool  message pool freequeue         757568
shared pool  qesblGF:bfm                       728

POOL         NAME                            BYTES
------------ -------------------------- ----------

等等數據。。。。。。。自己查看一下即可,我這由於篇幅所限,就輸出這麼多了。

 

 

 

--下麵SQL用於查看SGA中可進行手工調配參數的列表:

SQL> SELECT * FROM V$SGA_DYNAMIC_COMPONENTS;

COMPONENT                                                        CURRENT_SIZE   MIN_SIZE   MAX_SIZE USER_SPECIFIED_SIZE OPER_COUNT LAST_OPER_TYP LAST_
---------------------------------------------------------------- ------------ ---------- ----------
shared pool                                                        4194304000 4194304000          0          4194304000          0 STATIC                              
large pool                                                          134217728  134217728          0           134217728          0 STATIC                              
java pool                                                           134217728  134217728          0           134217728          0 STATIC                              
streams pool                                                        117440512  117440512          0           117440512          0 STATIC                              
DEFAULT buffer cache                                               1.2264E+10 1.2264E+10          0          1.2264E+10          2 SHRINK        MANUAL    07-4

KEEP buffer cache                                                   100663296          0          0           100663296          8 GROW          MANUAL    07-4
-10
RECYCLE buffer cache                                                 67108864          0          0            67108864          1 GROW          MANUAL    07-4
-10
DEFAULT 2K buffer cache                                                     0          0          0                   0          0 STATIC                              
DEFAULT 4K buffer cache                                                     0          0          0                   0          0 STATIC                              
DEFAULT 8K buffer cache                                                     0          0          0                   0          0 STATIC                              
DEFAULT 16K buffer cache                                                    0          0          0                   0          0 STATIC                              

COMPONENT                                                        CURRENT_SIZE   MIN_SIZE   MAX_SIZE USER_SPECIFIED_SIZE OPER_COUNT LAST_OPER_TYP LAST_
---------------------------------------------------------------- ------------ ---------- ----------
DEFAULT 32K buffer cache                                                    0          0          0                   0          0 STATIC                              
ASM Buffer Cache                                                            0          0          0          1.2465E+10          0 STATIC                  

 

 

查詢共享池大小:

SQL> show parameter shared_pool_size;

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
shared_pool_size                     big integer 4000M

 

SQL> SELECT NAME,TYPE,VALUE
  2      FROM V$PARAMETER A
  3      WHERE A.NAME='shared_pool_size';

NAME                                       TYPE          VALUE
-------------------- ----------------------------- ----------------------------
shared_pool_size                       6               4194304000

 

 

這裏順便說下,很多時候大家不知道數據字典是什麼,很多時候數據字典的名字很長,而且有些後麵又s,有些後麵沒有S,大家記錄不下來,此時大家隻需要知道大致是什麼就OK了,然後用下麵的基於視圖的總視圖去查詢視圖的實際名稱(我們以不知道共享池的視圖是什麼):

SQL> SELECT * FROM DICT T
  2  WHERE T.TABLE_NAME LIKE '%V$SHARED%';

 

TABLE_NAME                                COMMENTS
------------------------------ -------------------------------------------------------------------
V$SHARED_SERVER_MONITOR        Synonym for V_$SHARED_SERVER_MONITOR
V$SHARED_SERVER                       Synonym for V_$SHARED_SERVER
V$SHARED_POOL_RESERVED          Synonym for V_$SHARED_POOL_RESERVED
V$SHARED_POOL_ADVICE              Synonym for V_$SHARED_POOL_ADVICE
GV$SHARED_SERVER_MONITOR      Synonym for GV_$SHARED_SERVER_MONITOR
GV$SHARED_SERVER                     Synonym for GV_$SHARED_SERVER
GV$SHARED_POOL_RESERVED        Synonym for GV_$SHARED_POOL_RESERVED
GV$SHARED_POOL_ADVICE            Synonym for GV_$SHARED_POOL_ADVICE

 

其中GV$開頭的視圖是用於集群中的,我們一般隻關心V$開頭的信息;可能你連表的大致意思都不清楚,你隻大致記得有一個字段大致的名稱,如我們知道一個視圖內部字段的名稱有一個以SCN開頭的列,那麼我們這樣也可以反向給它定位:

SQL> SELECT T.TABLE_NAME, T.COLUMN_NAME
  2    FROM DICT_COLUMNS T
  3   WHERE T.COLUMN_NAME LIKE 'SCN%';

 

TABLE_NAME                     COLUMN_NAME
------------------------------ ------------------------------
ALL_SUMDELTA                   SCN
DBA_AUDIT_TRAIL                SCN
USER_AUDIT_TRAIL               SCN
DBA_AUDIT_STATEMENT            SCN
USER_AUDIT_STATEMENT           SCN
DBA_AUDIT_OBJECT               SCN
USER_AUDIT_OBJECT              SCN
DBA_AUDIT_EXISTS               SCN
DBA_FGA_AUDIT_TRAIL            SCN
DBA_COMMON_AUDIT_TRAIL         SCN
DBA_CAPTURE_PREPARED_TABLES    SCN
ALL_CAPTURE_PREPARED_TABLES    SCN
DBA_FILE_GROUP_TABLES          SCN
ALL_FILE_GROUP_TABLES          SCN
USER_FILE_GROUP_TABLES         SCN
V$RESTORE_POINT                SCN
V$RECOVERY_STATUS              SCN_NEEDED
V$LOGMNR_CONTENTS              SCN
V$XML_AUDIT_TRAIL              SCN
GV$RESTORE_POINT               SCN

 

         

 

這裏回到正題:清空共享池(OLTP係統運行時不要去操作,這個過程很影響整體運行)

SQL>ALTER SYSTEM FLUSH SHARED_POOL;

如果要查詢某過程或包的源碼,可以看一下係統的資源包:

SQL>SELECT * FROM USER_SOURCE t WHERE t.name = '過程或包的名字' ORDER BY LINE;

這些源碼信息在首次是不會裝入內存的,因為共享池的大小有限,調用時再裝入內存,而且也不會逃脫LRU的命運,若一些寫的很爛的SQL,就有可能把它替換出去,這個時候我們想做到的是啟動時直接裝入內存並不會被替換,ORACLE給我們一個KEEP方法,但是並非默認的,也就是安裝ORACLE後並不是默認就提供的這個包,如果你用具有DBA權限的人進去,不能使用DBMS_SHARED_POOL這個包(報:這個包不存在),說明還沒有創建,此時需要做一下操作,才能創建:

 

1、首先定位ORACLE_HOME的位置,我們沒有直接定位ORACLE_HOME的方式,除非是你自己安裝的,如果不知道,用下麵一個辦法:

SELECT * FROM V$PARAMETER P1
WHERE P1.NAME = 'spfile';

 

2、若沒有該目錄,使用CREATE SPFILE FROM PFILE;執行一下重啟OK就有了,得到該目錄後,假如得到如下:

D:/ORACLE10/PRODUCT/10.2.0/DB_1/DATABASE/SPFILEORCL102.ORA

那麼ORACLE_HOME上相推兩層得到:

D:/ORACLE10/PRODUCT/10.2.0/DB_1/

那麼要得到那個包的創建腳本就在:

D:/oracle10/product/10.2.0/db_1/RDBMS/ADMIN/DBMSPOOL.SQL

此時需要到安裝數據庫的機器上去執行,如果你本地有腳本當然也可以執行,但是注意:這個執行必須是在SQLPLUS中,PL/SQL中執行該腳本不好用。

 

3、執行方式:進入到安裝該數據庫的SQLPLUS下用SYS用戶登錄,該包需要創建在SYS用戶下。

SQL> @D:/oracle10/product/10.2.0/db_1/RDBMS/ADMIN/DBMSPOOL.SQL

程序包已創建。


授權成功。


視圖已創建。


程序包體已創建。

 

 

 

4、對於係統的大過程,可能第一次裝載比較緩慢,而且如果使用頻率較高,可以將其脫離LRU算法,並直接裝入內存,如果可以的話,做ORACLE啟動時觸發器,如果不行,就手動執行一下代碼:

BEGIN

   SYS.DBMS_SHARED_POOL.KEEP('存儲過程或包的名字');

END;

 

 

若想將某過程從內存中去除掉:

BEGIN

   SYS.DBMS_SHARED_POOL.UNKEEP('存儲過程或包的名字');

END;

 

 

5、此時查看緩衝池中是否裝載改對象:

SELECT name,owner,type
FROM v$db_object_cache where kept = 'YES'
AND NAME ='
過程或包的名字';//這個地方也可以用SQL片段來LIKE

 

 

查看共享池中執行的一些SQL包頭:

SELECT * FROM V$SQLAREA;

 

 

通不過上述的SQL得到HASH_VALUE或者ADDREDSS或者SQL_ID都可以通過以下視圖得到對應執行SQL的全部內容(當SQL較長的時候,V$SQLAREA隻保存前麵一部分,全部內容在該視圖中):

SELECT * FROM V$SQLTEXT_WITH_NEWLINES;

 

 

得到SQL的執行計劃:

SELECT * FROM V$SQL_PLAN;

 

 

得到對共享池設置的建議值,ORACLE根據實際運行情況,推薦值:

SELECT * FROM V$SHARED_POOL_ADVANCE;

 

 

還有些不是很常用的:

得到SQL綁定變量信息:

SELECT * FROM V$SQL_BIND_CAPTURE;

 

 

SQL占用共享池內存:

SELECT * FROM V$SQL_SHARED_MEMORY;

 

 

SQL消耗調用的統計信息:

SELECT * FROM V$SQLSTATS;

 

 

這裏隻是由預編譯->共享池->SGA的過程,對於SGA的內核隻是闡述了共享池的部分,下次說明SGA的另一大塊,Data Buffer,數據緩衝區,該區域在OLTP係統中非常重要。

 

最後補充話題,本來這個想在後麵說的,因為涉及一些其他內容,不過既然說到,就提一下吧,我們在OLTP要求使用綁定參數方式執行SQL如:

用應用程序的SQL應當是

String sql = “SELECT * FROM A WHERE ID=?”;

而不是

String sql = “SELECT * FROM A WHERE ID=”+id;

 

那麼這樣的情況我們該怎麼辦呢?當要查詢多個ID,使用IN的情況,或者同時修改多條記錄的操作,我們無疑想出最常規的三種辦法(我們先介紹常規方法,再介紹解決問題的方法):

 

方法1(拚串,放棄預編譯):

String sql = “SELECT * FROM A WHERE ID IN(”+keys+”)”;

付:該方法放棄預編譯,但是也是常規方法中的無奈之舉。

 

方法2(將參數個數動態化去預編譯)

StringBuffer sql = new StringBuffer(256);

sql.append(“SEELCT * FROM A WHERE ID IN(”);

for(….) {

   sql.append(“?”).append(“,”);

}

sql.deleteCharAt(sql.length()-1);

付:該方法比上一種稍微好一點,OLTP下一般情況下,我們常規方法中最少要這樣去完成,大家可以把ibatis的執行SQL日誌拿出來看下即可發現,ibatis對於動態參數個數也是這樣去完成的,對於並發度不算高的代碼段我們可以這樣使用,如果並發度高的代碼段,這樣使用我們也不會考慮。

 

方法3(循環提取。循環修改)

for(….) {

    ptmt.setInt(1,ID[i]);

    ptmt.executeQuery(“SELECT * FROM A WHERE ID=?”);

}

付:這在執行過程中往往是最“不應該使用”的辦法了;如果執行插入操作,我們會適當考慮攜帶批處理這樣去完成也是可以的,不過對於UPDATE和SELECT這類操作我們不該這樣使用的。

 

在這裏上述三種辦法,隻有第二種方法OLTP並發量不大的情況下可以使用,若並發量較大,且參數個數的動態性比較大,也應該考慮使用其他方法去實現,因為大家通過上述試驗和反向查詢後發現,問號個數的變化也會產生不同的SQL,共享池中仍然會造成很多的垃圾,隻是相對第一種方法概率降低了很多,而第三種方法基本是我們不考慮的。

 

我們說一下如果對於這樣的情況,批量執行過程中,我們該如何轉換,利用ORACLE的數組進行轉換,為此我們先在ORACLE內部提供一個函數,和數組類型,前序工作:

步驟1

創建數據類型(表格類型,也類似數組):

CREATE OR REPLACE TYPE MY_TABLE_TYPE IS TABLE OF VARCHAR2(8000);

 

步驟2

創建轉換函數(在網上很多地方可以找到類似代碼,我這隻是一個參考):

CREATE OR REPLACE FUNCTION SPLIT(SRC_STR IN VARCHAR2,

SPLIT_STR VARCHAR2) RETURN MY_TABLE_TYPE IS
  V_TABLE_STR MY_TABLE_TYPE := MY_TABLE_TYPE();
  V_TEMP_STR  VARCHAR2(
8000) := SRC_STR;
  V_SPLIT_STR VARCHAR2(
20) := SPLIT_STR;
  I           NUMBER :=
1;
  J           NUMBER :=
1;
BEGIN

  IF V_SPLIT_STR IS NULL THEN
      V_SPLIT_STR :=
',';--我們默認用逗號分隔  
  END IF;

  IF SRC_STR IS NULL OR SRC_STR = V_SPLIT_STR THEN
       RETURN V_TABLE_STR;
  END IF;

  V_TEMP_STR := LTRIM(V_TEMP_STR, V_SPLIT_STR);

  LOOP
        I := INSTR(V_TEMP_STR, V_SPLIT_STR, J);
        EXIT WHEN I =
0 OR J > LENGTH(V_TEMP_STR);
        V_TABLE_STR.EXTEND;
        V_TABLE_STR(V_TABLE_STR.COUNT) := SUBSTR(V_TEMP_STR, J, I - J);
        J := I + LENGTH(V_SPLIT_STR);
  END LOOP;

  IF J < LENGTH(V_TEMP_STR) THEN
        V_TABLE_STR.EXTEND;
        V_TABLE_STR(V_TABLE_STR.COUNT) := SUBSTR(V_TEMP_STR, J, LENGTH(V_TEMP_STR) - J +
1);
  END IF;

  RETURN V_TABLE_STR;

END SPLIT;

 

 

步驟3:(測試可用性,這裏假如數據都是按照逗號分隔的)

較高版本支持這樣的寫法:

SQL> SELECT * FROM TABLE(SPLIT('123,321',','));

 

COLUMN_VALUE

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

123

321

較低版本可以這樣寫:

SQL> SELECT * FROM TABLE(CAST(SPLIT('123,321,456', ',') AS MY_TABLE_TYPE));

 

COLUMN_VALUE

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

123

321

456

 

步驟4(程序應用):

String sql = “SELECT * FROM A WHERE ID IN(SELECT * FROM TABLE(SPLIT(?,',')))”;//當然對於低版本的數據庫,相應修改即可。

但是ORACLE有些時候會很傻的去使用HASH JOIN,因為他們他不知道你裏麵返回多少數據,而我們通過轉換回來的ID往往數據量很少,最多就是幾十行上百行,若目標表為一個大表,使用HASH JOIN的確是一件很浪費的事情,此時我們不願意這樣去做,因為很浪費CPU和臨時表空間(這其實是後麵要說的),我們一般需要強製指定查詢的方式來控製他走嵌套循環,讓大表根據小表去走索引,使用ORACLEHint來強製告訴它應該由小表引導大表執行,來保證SQL執行計劃的穩定性:

String sql = “SELECT /*+ordered use_nl(a2,a1)*/a2.* FROM A a1,(SELECT COLUMN_VALUE FROM TABLE(SPLIT(?,',')) a2 WHERE a1.ID = A2.COLUMN_VALUE”;

 

此時可能會問,這樣轉一次會不會很慢,是的,這不難會想想一個拆開字符串的過程,我們必然會消耗一點,不過要想到一次執行就是拆開一個字符串而已,而且我們傳入的字符串也不會太長,這個解析過程對於ORACLE來說還是沒啥問題的,不必擔心這個,而更加重要的提高了共享池的應用。

最後更新:2017-04-02 05:21:03

  上一篇:go gcc下的一個VC匯編轉換
  下一篇:go ajax +jsp+iframe無刷新上傳文件