DB2 SQL PL (SQL Procedural Language) 提供強大的過程化能力,但新手易陷入「能跑通即上線」誤區。本文提煉六大最佳實踐,覆蓋參數聲明、輸入校驗、游標語義、異常體系、臨時表治理、非法對象修復,助你編寫生產級高可用存儲過程。
核心參數對照表:
| 子句 | 可選值 | 性能影響 | 適用場景 |
|---|---|---|---|
| ALLOWED SQL | NO SQL / CONTAINS SQL / READS SQL DATA / MODIFIES SQL DATA | 遞增 | 按實際數據訪問類型最小化聲明 |
| DYNAMIC RESULT SETS n | 0~32767 | 編譯期驗證 | 預聲明返回集數量,防止漏返/多返 |
| DETERMINISTIC / NOT DETERMINISTIC | - | 優化器緩存 | 相同輸入必然相同輸出標 DETERMINISTIC |
| EXTERNAL ACTION / NO EXTERNAL ACTION | - | 並行限制 | 調用外部文件/網絡標 EXTERNAL ACTION |
實戰原則: 默認 MODIFIES SQL DATA 開銷最大,純查詢務必聲明 READS SQL DATA,無 SQL 操作聲明 NO SQL。
-- 字符型:NULL → 空串,並去尾部空格
SET p_name = RTRIM(COALESCE(p_name, ''));
-- 數值型:NULL → 0
SET p_rank = COALESCE(p_rank, 0);
-- 必填檢查:業務主鍵非空非零
IF p_id IS NULL OR p_name = '' OR p_age IS NULL THEN
SET p_status = 34100; -- 自定義錯誤碼
RETURN p_status;
END IF;
| 參數類型 | 業務是否必填 | 處理邏輯 |
|---|---|---|
| VARCHAR | 否 | COALESCE(param, '') |
| INT/BIGINT | 否 | COALESCE(param, 0) |
| VARCHAR | 是 | COALESCE(param, '') → 判斷 = '' |
| INT/BIGINT | 是 | 直接 IS NULL 判斷 |
CREATE PROCEDURE get_people(IN p_age INT)
DYNAMIC RESULT SETS 2
READS SQL DATA
LANGUAGE SQL
BEGIN
-- 返回給直接調用者 (可能是另一個 SP)
DECLARE c_caller CURSOR WITH RETURN TO CALLER FOR
SELECT name, age FROM person WHERE age < p_age;
-- 返回給最頂層客戶端應用 (JDBC/ODBC/CLI)
DECLARE c_client CURSOR WITH RETURN TO CLIENT FOR
SELECT name, age FROM person WHERE age > p_age;
OPEN c_caller; OPEN c_client;
END
避坑: 混用導致結果集「消失」或「錯位」,調試極難。統一規範:除非明確需要父 SP 消費,一律 TO CLIENT。
| 類型 | 執行流向 | 事務狀態 | 典型用途 |
|---|---|---|---|
| CONTINUE | 下一條語句 | 不變 | 記錄警告、統計、嘗試修復繼續 |
| EXIT | 終止 SP,返回調用者 | 不變 (已提交保留) | 業務錯誤、非致命異常、設置狀態碼返回 |
| UNDO | 回滾 SP 內所有 SQL → 終止 | 回滾 | 嚴重錯誤、數據不一致風險、必須原子性失敗 |
NOT FOUND : SQLCODE +100 / SQLSTATE 02000 (查無數據)SQLEXCEPTION : SQLCODE < 0 (錯誤)SQLWARNING : SQLCODE > 0 且 ≠ +100 (警告)-- 輸出參數:p_status (0=成功, 負值=SQLCODE, 正值=業務錯誤碼)
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION, SQLWARNING, NOT FOUND
BEGIN NOT ATOMIC
DECLARE v_sqlcode INT; DECLARE v_sqlstate CHAR(5);
SELECT SQLCODE, SQLSTATE INTO v_sqlcode, v_sqlstate FROM SYSIBM.SYSDUMMY1;
CASE v_sqlstate
WHEN '02000' THEN SET p_status = 5000; -- 未找到
WHEN '42724' THEN SET p_status = 3; -- 對象已存在
ELSE
IF v_sqlcode < 0 THEN SET p_status = v_sqlcode; END IF;
END CASE;
-- 可選:寫入審計表
-- INSERT INTO sp_audit_log (sp_name, sqlcode, sqlstate, ts) VALUES (...);
END;
-- 聲明自定義條件 (SQLSTATE 80xxx 保留給用戶)
DECLARE reset_ctx CONDITION FOR SQLSTATE '80100';
-- 處理器:重置上下文變量
DECLARE CONTINUE HANDLER FOR reset_ctx
BEGIN NOT ATOMIC
SET v_sqlcode = 0; SET v_sqlstate = '00000'; SET p_status = 0;
END;
-- 業務流程中隨時重置
SIGNAL reset_ctx;
-- ... 繼續執行 ...
DECLARE GLOBAL TEMPORARY TABLE SESSION.temp_emp (
id INT, name VARCHAR(30), dept_id INT
) ON COMMIT DELETE ROWS NOT LOGGED IN user_tbsp WITH REPLACE;
SESSION. 明確 Schema,避免歧義ON COMMIT DELETE ROWS 事務提交自動清空,保留結構NOT LOGGED 避免日誌爭用WITH REPLACE 同會話重複調用不報錯 (關鍵!)現象: SP 返回基於臨時表的游標 → 會話保持 → 臨時表不會隱式 DROP → 二次調用報「對象已存在」。
對策三選一:
WITH REPLACE (推薦,最簡單)DROP TABLE SESSION.temp_xxxDROP TABLE IF EXISTS SESSION.temp_xxx臨時表駐留內存 (或臨時表空間)。經驗閾值: 單表 > 10 萬行且高並發 (>50) 時,必須評估臨時表空間大小與內存壓力,必要時改用永久暫存表 + 分批處理。
SELECT RTRIM(r.routineschema) || '.' || RTRIM(r.routinename) AS sp_name,
'(' || RTRIM(r.routineschema) || '.P' || SUBSTR(CHAR(r.lib_id+10000000),2) || ')' AS pkg_name
FROM SYSCAT.ROUTINES r
WHERE r.routinetype = 'P'
AND ((r.origin = 'Q' AND r.valid != 'Y')
OR EXISTS (
SELECT 1 FROM SYSCAT.PACKAGES p
WHERE p.pkgschema = r.routineschema
AND p.pkgname = 'P' || SUBSTR(CHAR(r.lib_id+10000000),2)
AND p.valid != 'Y'
))
ORDER BY sp_name;
-- 單個
REBIND PACKAGE TEST.P3550884 RESOLVE ANY;
-- 批量 (Shell 腳本循環)
for pkg in $(db2 -x "SELECT 'P'||SUBSTR(CHAR(lib_id+10000000),2) FROM SYSCAT.ROUTINES WHERE valid!='Y'"); do
db2 "REBIND PACKAGE $pkg RESOLVE ANY"
done
前置條件: 所有依賴對象 (表/視圖/函數) 必須已 VALID,否則 REBIND 仍失敗。
SELECT RTRIM(viewschema) || '.' || RTRIM(viewname) AS view_name
FROM SYSCAT.VIEWS WHERE valid = 'X' ORDER BY view_name;
存儲過程是業務邏輯的「最後一道防線」,工程化標準不僅關乎性能,更關乎可維護性與故障可恢復性。建議將六大實踐編碼為團隊 Code Snippet / SonarQube 規則 / CI 檢查門禁,讓最佳實踐成為肌肉記憶而非文檔陳列。