1.超長的PL/SQL代碼
影響:可維護性,性能
症狀:
在復雜的企業應用中,存在動辄成百上千行的存儲過程或上萬行的包。
為什麼是最差:
太長的PL/SQL代碼不利於閱讀,第三方工具在調試時也會出現代碼行混亂等問題。PL/SQL存儲對象(存儲過程、包、函數、觸發器等)行數上限約為6000000行,但實際工作中,當包大小超過5000行就會出現調試問題。
解決之道:
PL/SQL代碼在執行前會被加載到shared pool中,shared pool以字節為單位,UNIX下為64K,桌面環境下為32K,可以通過查詢數據字典USER_OBJECT_SIZE的PARSED_SIZE字段查看對象大小。對於較大的包,應采用拆包策略,抽取復用部分,減少重復代碼;對於較大的存儲過程,應將存儲過程組織到包中,易於管理;對於較大的匿名塊,應將匿名塊重新定義成子過程保存在數據庫中。
2.脫離控制的全局變量
影響:可維護性
症狀:在包中使用了全局變量,在多個位置對全局變量進行操作。
create OR REPLACE PACKAGE BODY PKG_TEST IS
GN_全局變量 NUMBER(12, 2);
PROCEDURE 過程A IS
BEGIN
GN_全局變量:=1;
END;
PROCEDURE 過程B IS
BEGIN
GN_全局變量:=2; -- 這裡對全局變量進行了另外的操作
END;
為什麼是最差:
全局變量可以在整個包范圍內被訪問到,因此對全局變量的跟蹤和調試會比較困難。如果變量是在package中定義的,變量還可以被其他包訪問,這將會更為危險。
解決之道:
減少或取締全局變量的使用,對於要在過程間交互的變量,通過參數傳遞來實現。如果必須使用全局變量,應對全局變量進行get/set函數封裝,規范對全局變量的訪問。
3.PL/SQL中嵌入復雜SQL語句
影響:可維護性
症狀:
在PL/SQL代碼中嵌入SQL語句,如:
...
PROCEDURE 過程A IS
BEGIN
update T_A SET COL1 = 10;
END;
PROCEDURE 過程B IS
BEGIN
delete FROM T_A where COL1=10;
END;
...
為什麼是最差:
PL/SQL代碼中嵌入SQL語句使得代碼含義變得難於閱讀和理解
在多個位置對表進行訪問,不利於SQL優化
解決之道:
將分散SQL語句進行封裝,例如上例中的刪除語句,可以封裝為“prc_刪除T_A()”過程參數為T_A的type類型,對T_A的刪除操作都委托此過程處理,當T_A表增加或刪除字段時,主要的變化都集中在這些過程中,對其他邏輯影響較少
對SQL的優化集中在封裝的過程中
4.“異常”的異常處理
影響:可維護性,健壯性
症狀:我們來看下面的代碼:
PROCEDURE 過程A(錯誤代碼 out varchar2,錯誤信息 out varchar2) IS
BEGIN
...
update T_A SET COL1 = 10;
select ...FROM T_A where ...;
delete FROM T_A where COL1 = 20;
...
EXCEPTION
WHEN OTHERS THEN
...
END;
為什麼是最差:
整個過程只有一個WHEN OTHERS 的異常段,示例中的三個語句發生的異常只能被最外層捕捉,無法區分發生異常的種類和位置。
解決之道:
不使用WHEN OTHERS捕捉所有異常,例如不應該捕捉NO_DATA_FOUND異常,使用專用的Exception來捕捉特定的異常。
聲明自己的異常處理機制,處理與業務相關的異常,將業務異常與系統運行期異常分開處理。
自定義完整的異常信息,異常信息中包含異常發生時的場景。
5.固定的變量長度和變量類型
影響:可維護性
症狀:當聲明基於字段類型的變量時,尤其是varchar2類型,直接使用固定長度聲明。
為什麼是最差:
這種硬編碼的變量大小很可能與數據庫中實際大小不符
如果字段的類型、大小等發生變化,還需要到PL/SQL中調整變量
解決之道:
使用%Type聲明與字段類型相關的變量。
6.不做單元測試
影響:健壯性
症狀:PL/SQL代碼中蘊含大量的業務邏輯,這些邏輯編寫完畢後,沒有提供合適的單元測試用例用於驗證。
為什麼是最差: 不做單元測試的危害這裡就不再廢話了。
解決之道:
PL/SQL並沒有提供諸如JUnit之類易用的單元測試工具。現在有一些開源工具可以使用。使用utPLSQL(http://utplsql.sourceforge.Net/)工具進行單元測試,或DBUnit進行二次開發,滿足不同應用的需要。
7.使用代碼值而不使用代碼名稱
影響:可維護性
症狀:我們看下面的代碼:
方法1:
V_sex:=’1’; -- 男
方法2:
CONST_MALE CONSTANT VARchar2(1) := ’1’; -- 定義常量 男
V_sex:=CONST_MALE;
為什麼是最差:
從例子中可以看出,同樣是使用性別,方法1是直接使用代碼值,方法2是使用常量,看上去似乎方法2要比方法1麻煩一些,但方法2比方法1更為直觀,代碼的可讀性也更好,代碼的閱讀者不需要關注“1”代表什麼含義。
當其他項目男性性別定義修改為“2”時,采用方法1編碼的程序需要仔細查找每一段代碼,容易產生錯誤,而采用方法2編碼的程序只修改常量定義即可。
解決之道:
將常量定義放入到公共的代碼包中,供其他程序共享,所有涉及到代碼值的比較、引用等都必須使用常量名,而不能直接書寫代碼值。對於一些復雜的代碼值間的關系可以進一步封裝,以函數的方式提供調用。
8.不對PL/SQL對象進行配置管理
影響:可維護性
症狀:PL/SQL對象(package、package body、trigger、procedure、type、type body、函數等)的代碼沒有使用配置管理工具進行維護和更新。
為什麼是最差:
因為Oracle內部結構的差異,對象的管理具有一定的難度,尤其是在並行開發的情況下。
對象職責劃分不清,造成多人同時修改一個對象,在編譯時,如果後來者沒有獲取最新的代碼,會造成前一個開發人員修改的代碼被覆蓋。
Oracle對象不能追溯既往,數據庫中只能保存最新
解決之道:
規范開發過程,以配置管理工具上的PL/SQL代碼為最新。
使用第三方插件減少同步工作量,如PL/SQL Developer下的VCS版本控制插件。
9.IF … ELSE …的壞味道
影響:可維護性
症狀:大量使用IF … ELSE
為什麼是最差:
大量存在IF/ELSE,造成代碼邏輯混亂、不易修改。無論是PL/SQL還是其他編程語言,這種代碼都已經飄著“bad smell”了。
解決之道:
使用Oracle數據庫的繼承特性,通過type實現對象的繼承,利用策略模式封裝差異,對外提供統一的調用接口
將頻繁使用的IF/ELSE代碼重構為單獨的過程或函數,供其他代碼復用
10.在非自治事務中控制事務
影響:數據一致性
症狀:
在PL/SQL非自治事務代碼中控制事務,例如:
PROCEDURE 過程A(錯誤代碼 out varchar2,錯誤信息 out varchar2) IS
BEGIN
...
SAVEPOINT A;
update T_A SET COL1 = 10;
COMMIT;
delete FROM T_A where COL1 = 20;
ROLLBACK TO A;
...
EXCEPTION
WHEN OTHERS THEN
...
END;
為什麼是最差:
這種行為是我認為最差實踐中危害最大的一種。隨處可見的事務控制代碼會造成數據不一致,引發的問題難於跟蹤和調試。
解決之道:
由調用者決定何時提交或回滾事務。
對於需要特殊事務管理的過程如記載日志,使用自治事務。
11.不使用綁定變量
影響:性能
症狀:直接使用值而不使用綁定變量進行查詢。尤其是在拼寫sql的程序中,這種情況更突出。
為什麼是最差:
這是一個常見問題,當代碼中大量充斥固定的代碼值時,數據庫引擎每次都需要重新解析,不能使用既有的執行計劃。
解決之道:對於這種經常執行的語句,使用綁定變量而非實際參數值執行。
12.慎用ROWNUM=1
影響:可維護性、數據一致性
症狀:在讀取數據時,有時只需要取一行,這時where條件中就會用到ROWNUM=1。
為什麼是最差:
之所以將這個實踐評成最差,是因為筆者在實際工作中曾經遇到過這類問題,跟蹤和調試都很困難。ROWNUM本身的處理順序是在ORDER BY 之前,所以當ROWNUM=1時產生的結果很可能是隨機的。
解決之道:了解要查詢數據的含義,使用其他條件限制結果集。
13.靈活的動態SQL
影響:可維護性、性能
症狀:execute IMMEDIATE ‘select A FROM TAB1’ INTO v_a;
為什麼是最差:動態SQL失去了編譯期檢查能力,將發生問題的可能性推遲到運行期。動態SQL也不利於優化,因為只有在運行期才能得到完整的SQL語句。
解決之道:盡量避免使用動態SQL,對於易變的業務邏輯可以抽取到中間層實現。
14.對ROWID進行訪問
影響:數據一致性
症狀:使用ROWID作為數據更新、刪除的where條件
為什麼是最差:
ROWID屬於Oracle底層存儲結構,會隨著數據的遷移、導入、導出發生變化,而業務邏輯則不應依賴底層存儲結構。
解決之道:使用主鍵進行數據操作。