DB2 存儲過程開發最佳實踐2

 清單2:聲明異常處理器

  DECLARE handler-type HANDLER FOR condition handler-action

  異常處理器類型(handler-type)有以下幾種:

  CONTINUE 在處理器操作完成之後,會繼續執行產生這個異常語句之後的下一條語句。

  EXIT 在處理器操作完成之後,存儲過程會終止,並將控制返回給調用者。

  UNDO 在處理器操作執行之前,DB2會回滾存儲過程中執行的SQL操作。在處理器操作完成之後,存儲過程會終止,並將控制返回給調用者。

  異常處理器可以處理基於特定SQLSTATE值的定製異常,或者處理預定義異常的類。預定義的3種異常如下所示:

  NOT FOUND 標識導致SQLCODE值爲+100或者SQLSATE值爲02000的異常。這個異常通常在SELECT沒有返回行的時候出現。

  SQLEXCEPTIOIN 標識導致SQLCODE值爲負的異常。

  SQLWARNING 標識導致警告異常或者導致+100以外的SQLCODE正值的異常。

  如果產生了NOT FOUND 或者SQLWARNING異常,並且沒有爲這個異常定義異常處理器,那麼就會忽略這個異常,並且將控制流轉向下一個語句。如果產生了SQLEXCEPTION異常,並且沒有爲這個異常定義異常處理器,那麼存儲過程就會失敗,並且會將控制流返回調用者。

  以下示例聲明瞭兩個異常處理器。 EXIT處理器會在出現SQLEXCEPTION 或者SQLWARNING異常的時候被調用。EXIT處理器會在終止SQL程序之前,將名爲stmt的變量設爲"ABORTED",並且將控制流返回給調用者。UNDO處理器會將控制流返回給調用者之前,回滾存儲過程體中已經完成的SQL操作。

  清單3:異常處理器示例

  DECLARE EXIT HANDLER FOR SQLEXCEPTION, SQLWARNING SET stmt = 'ABORTED';

  DECLARE UNDO HANDLER FOR NOT FOUND;

  如果預定義異常集不能滿足需求,就可以爲特定的SQLSTATE值聲明定製異常,然後再爲這個定製異常聲明處理器。語法如下:

  清單4:定製異常處理器

  DECLARE unique-name CONDITION FOR SQLSATE 'sqlstate'

  處理器可以由單獨的存儲過程語句定義,也可以使用由BEGIN…END塊界定的複合語句定義。注意在執行符合語句的時候,SQLSATE和SQLCODE的值會被改變,如果需要保留異常前的SQLSATE和SQLCODE,就需要在執行復合語句的第一個語句把SQLSATE和SQLCODE賦予本地變量或參數。

  通常,我們會爲存儲過程定義一個執行狀態的輸出參數(例如:poGenStatus)。

  根據這個輸出狀態,可以表明存儲過程是否正確執行完畢。我們需要定義一些異常處理器爲這個輸出參數賦值。下面是一個例子:

  清單5:定義爲輸出參數賦值的異常處理器

  -- Generic Handler DECLARE CONTINUE HANDLER FOR SQLEXCEPTION, SQLWARNING, NOT FOUND BEGIN NOT ATOMIC -- Capture SQLCODE & SQLSTATE SELECT SQLCODE, SQLSTATE INTO hSqlcode, hSqlstate FROM SYSIBM.SYSDUMMY1; -- Use the poGenStatus variable to tell the procedure -- what type of error occurred CASE hSqlstate WHEN '02000' THEN SET poGenStatus=5000; WHEN '42724' THEN SET poGenStatus=3; ELSE IF (hSqlCode < 0) THEN SET poGenStatus=hSqlCode; END IF; END CASE; END;

  上面的異常處理器會在出現SQLEXCEPTION, SQLWARNING, NOT FOUND異常的時候觸發。異常處理器會取出當前的SQLCODE, SQLSTATE,然後根據它們的值來設置輸出參數(poGenStatus)的值。

  我們還可以定製一些異常處理器。例如,我們可以定義一些對參數進行初始化的異常處理器。這裏,異常處理器可以看作是一個供存儲過程自己調用的內部函數。下面是這種情況的一個例子:

  清單6:供存儲過程自己調用的內部函數

  ---------------------- -- CONDITION declaration ----------- -- (80100~80199) SQLCODE & SQLSTATE DECLARE sqlReset CONDITION for sqlstate '80100'; ----------------------------------------------------- -- EXCEPTION HANDLER declaration ----------------------------------------------------- -- Handy Handler DECLARE CONTINUE HANDLER FOR sqlReset BEGIN NOT ATOMIC SET hSqlcode = 0; SET hSqlstate = '00000'; SET poGenStatus = 0; END; ………… ----------------------------------------------------- -- Procedure Body ----------------------------------------------------- SIGNAL sqlreset; -- insert the record …………

  上面定製的異常處理器負責對參數hSqlcode,hSqlstate和poGenStatus初始化。當我們在程序中需要對它們初始化時,我們只需要調用SIGNAL sqlreset就可以了。

  最佳實踐 5:合理使用臨時表

  我們在儲存過程開發中經常使用臨時表。合理的使用臨時表可以簡化程序的編寫,提供執行效率,然而濫用臨時表同樣也會使得程序運行效率降低。

  臨時表一般在如下情況下使用:

  1. 臨時表用於存儲程序運行中的臨時數據。例如,如果在一個程序中第一條查詢語句執行的結果會被後續的查詢語句用到,那麼我們可以把第一次查詢的結果存儲在一個臨時表中供後續查詢語句使用,而不是在後續查詢語句中重新查詢一次。如果第一條查詢語句非常複雜和耗時,那麼上面的策略是非常有效的。

  2. 臨時表可以用於存儲在一個程序中需要返回多次的結果集。例如,程序中有一個很耗資源的多表查詢,同時,該查詢在程序中需要執行多次,那麼就可以把第一次查詢的結果集存儲在臨時保中,後續的查詢只需要查臨時表就可以了。

  3. 臨時表也可以用於讓SQL訪問非關係型數據庫。例如,可以編寫程序把非關係型數據庫中的數據插入到一個全局臨時表中,那麼我們就可以對其數據進行查詢。

  我們可使用 DECLARE GLOBAL TEMPORARY TABLE 語句來定義臨時表。DB2的臨時表是基於會話的,且在會話之間是隔離的。當會話結束時,臨時表的數據被刪除,臨時表被隱式卸下。對臨時表的定義不會在SYSCAT.TABLES中出現 下面是定義臨時表的一個示例:

清單7:定義臨時表

  DECLARE GLOBAL TEMPORARY TABLE gbl_temp LIKE person ON COMMIT DELETE ROWS NOT LOGGED IN usr_tbsp

  此語句創建一個名爲 gbl_temp 的用戶臨時表。定義此用戶臨時表 所使用的列的名稱和說明與 person 的列的名稱和說明完全相同。

  清單8:創建有兩個字段的臨時表

  DECLARE GLOBAL TEMPORARY TABLE SESSION.TEMP2 ( ID INTEGER default 3, NAME CHAR(30) ) --WITH REPLACE NOT LOGGED; --IN USER_TEMP_01;

  此語句創建了一個有兩個字段的臨時表。

  理論上臨時表是不需要顯示DROP的,因爲它是基於會話的,當臨時表基於的連接關閉的時候,臨時表也就不存在了。但是在實際開發中會有一些情況需要我們對臨時表加以注意。

  一種情況就是被調用的存儲過程的返回值是一個基於臨時表的結果集。當存儲過程執行完畢的時候,臨時表並不會消失,因爲返回的結果集相當於一個指針,指向臨時表所在的內存地址,此時臨時表是不會被DROP掉的。這種情況下,既不能在存儲過程中刪除這個臨時表,也不應該由客戶應用顯示的刪除臨時表,這就容易出現一些問題。下面我們通過一個例子來說明這個問題。

  下面示例代碼是返回臨時表的存儲過程(get_temp_table):

  清單9:返回臨時表的存儲過程

  ----------------------------------------------------- -- TEMPORARY TABLE & CURSOR declaration ----------------------------------------------------- DECLARE GLOBAL TEMPORARY TABLE SESSION.TEMP ( ID INTEGER, NAME CHAR(30) ) --WITH REPLACE NOT LOGGED; P2: BEGIN DECLARE R_CRSR CURSOR WITH RETURN TO CLIENT FOR SELECT * FROM SESSION.TEMP FOR READ ONLY; INSERT INTO SESSION.TEMP VALUES(1,piName); OPEN R_CRSR; END P2;

  存儲過程中聲明瞭有兩個字段的臨時表TEMP,聲明瞭一個遊標R_CRSR返回臨時表中所有記錄,最後在臨時表中插入兩條記錄。

  圖2:程序第一次執行的結果

 DB2 存儲過程開發最佳實踐(圖三)

  可以從圖中看出,運行結果是我們期望的。那麼如果我們再運行一次,會有什麼結果呢?下圖是其運行結果:

  圖3:程序再次執行的結果

  DB2 存儲過程開發最佳實踐(圖四)

  第二次執行的時候程序卻出錯了,這是因爲在同一個連接中,臨時表並沒有被DROP掉,所以在第二次調用存儲過程的時候就會出現臨時表已經存在的錯誤。

  另外一種情況,就是很多時候例如在websphere中通過JDBC連接數據庫時使用了連接池的技術,這帶來了一些效率的提升,同時在某些情況下也容易讓人誤解。客戶應用程序中關閉了數據庫連接,但是並不一定真正關閉了數據庫連接,如果客戶應用程序使用了臨時表而數據庫連接並沒有關閉,那麼臨時表就不會被DROP。當連接池把這個連接分給另一個客戶程序的時候,新的客戶程序仍然可以使用舊的臨時表,這不是我們希望的。如果想避免上述問題,可以在創建臨時表時,加上WITH REPLACE;或者根據業務邏輯在合適的地方顯示的DROP臨時表。

  下面是使用WITH REPLACE創建臨時表的執行情況。

  圖4:使用WITH REPLACE創建臨時表

  DB2 存儲過程開發最佳實踐(圖五)

  可以看出在一個連接裏面,多次調用存儲過程get_temp_table,也不會出現問題。臨時表在某些情況下也是需要避免使用的。大家知道臨時表是存放在內存中的,如果一個臨時表有上萬或者十幾萬條記錄,同時程序的併發數很大,那麼在內存中建立的臨時表耗費的資源就很龐大,此時數據庫的性能會急劇下降,甚至會導致數據庫崩潰。因此,大家在使用臨時表的時候,需要考慮它對資源的耗費,避免盲目使用臨時表。

最佳實踐 6:尋找並rebind 非法的存儲過程

  存儲過程會因爲其涉及和引用的對象發生了改變而導致其非法(invalid),例如:修改了表結構,導致引用該表的存儲過程非法,或者重新編譯一個存儲過程,會使調用這個存儲過程的父存儲過程非法。此時我們需要對非法的存儲過程重新編譯(rebind)。但是,對非法的存儲過程進行rebind的時候,需要確定其引用的對象是合法的,否則非法的存儲過程也不能rebind成功。

  這裏我們介紹一下發現和rebind非法存儲過程的方法。我們是通過判斷SYSCAT.routines中VALID字段的值來查找非法存儲過程的。下面是查找非法存儲過程的一段代碼:

  清單10:查找非法存儲過程

  SELECT RTRIM(r.routineschema) || '.' || RTRIM(r.routinename) AS spname , ' ( '|| RTRIM(r.routineschema) || '.' || 'P'||SUBSTR(CHAR(r.lib_id+10000000),2)||' )' FROM SYSCAT.routines r WHERE r.routinetype = 'P' AND ((r.origin = 'Q' AND r.valid != 'Y') OR EXISTS ( SELECT 1 FROM syscat.packages WHERE pkgschema = r.routineschema AND pkgname = 'P'||SUBSTR(CHAR(r.lib_id+10000000),2) AND valid !='Y' ) ) ORDER BY spname;

  獲得的結果如下:

  清單11:查找非法存儲過程的結果

  SPNAME ---------------------------------- TEST.DEMO_INFO_8 (TEST. P3550884)

  可以使用下面的命令rebind它們

  清單12:Rebind 非法存儲過程語法

  rebind package packagename resolve any@

  Packagename就是查詢結果中括號裏的值。例如,如果rebind上面查出來的存儲過程。我們只需要執行下面語句

  清單13:Rebind 非法存儲過程

  rebind package TEST.P3550884 resolve any@

  當然,如果此存儲過程程序本身有問題,需要先修改存儲過程代碼後再進行編譯。

  類似的,通過下面的代碼可以獲得非法的視圖。

  清單14:獲得非法的視圖

  SELECT RTRIM(viewschema) || '.' || RTRIM(viewname) AS viewname FROM SYSCAT.views WHERE valid = 'X' ORDER BY viewname;

  結束語

  本文介紹了我們在 DB2 存儲過程開發中經常用到的一些技巧。同時這些技巧也是編寫優秀存儲過程的基本要求。本文介紹的一些技巧只是揭開了高效使用 DB2 的冰山一角。DB2 爲我們提供了豐富和強大的功能。在使用 DB2 的時候,我們應當深入理解其原理,找出更多的最佳實踐與大家分享。

  參考資料

  Red book: IBM DB2 UDB Command Reference Version 8

  Red Book:IBM DB2 UDB Application Development Guide

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章