首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 數據庫知識 >  >> Oracle 資料庫 >> Oracle 資料庫基礎

PL/SQL 開發 14 大反模式與最佳實踐指南

     編輯:Oracle 資料庫基礎

前言

PL/SQL 是 Oracle 資料庫的核心程式語言,但許多專案因忽視基本原則而陷入維護困境。本文整理 14 種常見的「最差實踐」,分析其危害並提供具體重構方案,助你編寫健壯、高效、可維護的程式碼。

反模式一:超長巨無霸程式碼

⚠ 症狀

  • 單一 Package 超過 5,000 行,Stored Procedure 動輒數百行。
  • 第三方除錯工具出現行號錯亂、效能低落。

💥 危害

Shared Pool 載入壓力大(UNIX 64K/桌面 32K 為單位),超過 5,000 行即觸發除錯問題;邏輯耦合嚴重,變更風險極高。

✅ 解決之道

  • 拆包策略:抽取共用邏輯為獨立 Package,減少重複代碼。
  • 程序模組化:將巨大 Procedure 切分為 Package 內的私有子程序。
  • 匿名區塊持久化:將大型匿名區塊轉為預存子程序存入資料庫。
  • 監控指標:查詢 USER_OBJECT_SIZE.PARSED_SIZE 追蹤物件大小。

反模式二:失控的全域變數

⚠ 症狀

CREATE OR REPLACE PACKAGE BODY pkg_bad IS
  g_global_var NUMBER;
  PROCEDURE proc_a IS BEGIN g_global_var := 1; END;
  PROCEDURE proc_b IS BEGIN g_global_var := 2; END; -- 遠端修改
END;

💥 危害

Package Specification 中宣告的變數對所有 Session 可見,導致狀態不可預測、除錯極難、併發安全風險極高。

✅ 解決之道

  • 優先使用 參數傳遞 替代全域狀態共享。
  • 若必須使用,封裝 GET/SET 函數,集中控制存取邏輯與驗證。

反模式三:SQL 硬編碼散落在業務邏輯中

⚠ 症狀

PROCEDURE proc_a IS BEGIN UPDATE t SET col = 10; END;
PROCEDURE proc_b IS BEGIN DELETE FROM t WHERE col = 10; END;

💥 危害

同一張表的存取邏輯分散在多處,SQL 調優困難,結構變更影響面大。

✅ 解決之道

  • 建立 資料存取層 Package:如 pkg_t_api.ins_t、pkg_t_api.del_t 封裝所有 DML。
  • 表結構變更時,僅需修改 API 層,業務邏輯層不受影響。
  • SQL 調優集中在 API 內部進行。

反模式四:濫用 WHEN OTHERS 吞噬例外

⚠ 症狀

EXCEPTION
  WHEN OTHERS THEN
    log_error(SQLCODE, SQLERRM); -- 丟失錯誤發生位置與類型
END;

💥 危害

無法區分 NO_DATA_FOUND、DUP_VAL_ON_INDEX 等具體錯誤,掩蓋真實問題,導致資料不一致難以追蹤。

✅ 解決之道

  • 針對預期業務錯誤定義 自訂例外(DECLARE e_biz_error EXCEPTION; PRAGMA EXCEPTION_INIT(e_biz_error, -20001);)。
  • 區分 系統例外(讓其向上拋出或記錄完整堆疊)與 業務例外(友善回饋給呼叫端)。
  • 例外訊息必須包含「場景、關鍵參數、錯誤代碼」。

反模式五:變數長度與型別硬編碼

⚠ 症狀

v_name VARCHAR2(30); -- 表欄位實際為 VARCHAR2(50)

💥 危害

結構變更時需同步修改所有程式碼,極易遺漏導致 VALUE_ERROR。

✅ 解決之道

全面使用 %TYPE(單欄位)與 %ROWTYPE(整列)錨定定義:

v_name  employees.last_name%TYPE;
r_emp   employees%ROWTYPE;

反模式六:完全缺乏單元測試

✅ 解決之道

  • 導入 utPLSQL(開源單元測試框架,支援 CI/CD 整合)。
  • 為每個公共 API 編寫測試案例,覆蓋正常、邊界、異常路徑。
  • 測試代碼與產品代碼分離,部署時排除。

反模式七:魔術數值/字串到處硬編碼

⚠ 症狀

IF v_status = '1' THEN ... -- '1' 代表什麼?

✅ 解決之道

  • 建立 常數 Package(pkg_constants)集中管理:c_status_active CONSTANT VARCHAR2(1) := '1';
  • 所有比較、賦值均引用常數名,修改時只需改一處。
  • 複雜代碼關係封裝為函數:pkg_constants.is_active(p_status)。

反模式八:PL/SQL 物件無版控管理

💥 危害

併行開發覆蓋代碼、無歷史追溯、部署版本不一致。

✅ 解決之道

  • 以 Git/SVN 為單一真實來源,資料庫物件僅為部署結果。
  • 使用 PL/SQL Developer VCS、Gitora 等工具實現 IDE 級版控整合。
  • 建立 編譯/部署腳本自動化 流程。

反模式九:IF-ELSE 地獄

✅ 解決之道

  • 策略模式:利用 Oracle 物件型別繼承(TYPE ... UNDER),將差異邏輯封裝在子型別方法中,對外統一介面。
  • 查表法:將條件分支轉為設定表驅動,程式碼僅負責查表路由。
  • 高頻分支重構為獨立私有函數。

反模式十:非自治交易中手動控制 COMMIT/ROLLBACK

⚠ 症狀

SAVEPOINT a; UPDATE ...; COMMIT; DELETE ...; ROLLBACK TO a;

💥 危害

破壞交易邊界,導致資料不一致、死鎖、邏輯極難追蹤。這是危害最大的反模式。

✅ 解決之道

  • 交易控制上移至呼叫端(應用層或最外層 API)。
  • 真正需要獨立提交的場景(如審計日誌),使用 PRAGMA AUTONOMOUS_TRANSACTION。

反模式十一:不使用繫結變數

💥 危害

SQL 硬解析風暴,Shared Pool 暴增,執行計劃無法複用,效能崩潰。

✅ 解決之道

所有動態/靜態 SQL 均使用繫結變數:

EXECUTE IMMEDIATE 'SELECT * FROM t WHERE id = :b1' INTO v USING p_id;

反模式十二:盲目使用 ROWNUM = 1

💥 危害

ROWNUM 產生於 ORDER BY 之前,結果具隨機性,極易導致業務邏輯錯誤且難以重現。

✅ 解決之道

  • 明確業務語義:用 FETCH FIRST 1 ROW ONLY (12c+) 或 MAX/MIN、ROW_NUMBER() OVER 確定性取值。
  • 若需任意一筆,加上 ORDER BY 確保穩定。

反模式十三:濫用動態 SQL

💥 危害

失去編譯期檢查、SQL Injection 風險、執行計劃無法預測、除錯困難。

✅ 解決之道

  • 靜態 SQL 能解決的堅決不用動態 SQL。
  • 極少數確需動態場景(DDL、表名變數),嚴格驗證輸入、使用 DBMS_ASSERT 防注入。
  • 易變業務邏輯下沉中間層(Java/.NET)處理。

反模式十四:直接操作 ROWID

💥 危害

ROWID 為物理存儲地址,隨 MOVE、EXP/IMP、分區維護等操作改變,業務邏輯不應依賴底層結構。

✅ 解決之道

永遠使用 主鍵 或 唯一鍵 進行資料定位與 DML 操作。

總結檢查清單

#反模式核心對策
1超長程式碼拆包、模組化、持久化匿名區塊
2失控全域變數參數傳遞、GET/SET 封裝
3SQL 硬編碼分散資料存取層 API 化
4WHEN OTHERS 吞噬例外自訂例外、分層處理、豐富錯誤上下文
5型別長度硬編碼%TYPE / %ROWTYPE 錨定
6無單元測試utPLSQL、CI/CD 整合
7魔術數值硬編碼常數 Package 集中管理
8無版控管理Git 為真實來源、自動化部署
9IF-ELSE 地獄策略模式/查表法/重構函數
10手動交易控制交易上移、自治交易僅用於日誌
11無繫結變數全面參數化 SQL
12ROWNUM=1 隨機性確定性排序取值
13濫用動態 SQL靜態優先、中間層承擔變化
14直接用 ROWID主鍵/唯一鍵定位
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved