測試環境:Windows 2003,Xeon 5140 2.33GHz 4核,4GB 內存。表 tacustomer 約 200 萬行。對比三種寫法提取生日年份:
| 寫法 | SQL 示例 | 耗時(秒) | 倍數 |
|---|---|---|---|
| 純 SQL 字符串拼接 | COUNT(certificatetype||certificateno||birthday) | 8.6 | 1.0x (基準) |
| 自定義函數 v1(逐字符校驗) | COUNT(f_extract_birthday_v1(...)) | 19.8 | 2.3x |
| 自定義函數 v2(CAST 捕獲異常) | COUNT(f_extract_birthday_v2(...)) | 58.0 | 6.7x |
CAST(... AS NUMBER) + 異常捕獲,異常處理機制額外昂貴,導致更慢。CREATE OR REPLACE FUNCTION f_extract_birthday_v1 (
p_id VARCHAR2,
p_birthday VARCHAR2
) RETURN VARCHAR2 IS
v_len PLS_INTEGER := LENGTH(p_id);
v_yyyy VARCHAR2(4);
BEGIN
IF p_birthday IS NOT NULL THEN
RETURN SUBSTR(p_birthday, 1, 4);
END IF;
IF v_len = 15 AND p_id BETWEEN '000000000000000' AND '999999999999999' THEN
RETURN '19' || SUBSTR(p_id, 7, 2); -- 一代身份證
ELSIF v_len = 18 AND SUBSTR(p_id, 1, 17) BETWEEN '00000000000000000' AND '99999999999999999' THEN
RETURN SUBSTR(p_id, 7, 4); -- 二代身份證
END IF;
RETURN NULL;
END;
CREATE OR REPLACE FUNCTION f_extract_birthday_v2 (...) RETURN VARCHAR2 IS
v_num NUMBER;
BEGIN
BEGIN
v_num := CAST(p_id AS NUMBER); -- 非全數字拋異常
-- 此處省略後續邏輯
EXCEPTION WHEN VALUE_ERROR THEN
RETURN NULL;
END;
END;
SELECT COUNT(
CASE
WHEN birthday IS NOT NULL THEN SUBSTR(birthday,1,4)
WHEN LENGTH(certificateno)=15 AND TRANSLATE(certificateno,'0123456789','##########') IS NULL
THEN '19'||SUBSTR(certificateno,7,2)
WHEN LENGTH(certificateno)=18 AND TRANSLATE(SUBSTR(certificateno,1,17),'0123456789','##########') IS NULL
THEN SUBSTR(certificateno,7,4)
END
) FROM tacustomer WHERE ROWNUM < 2000000;
預期耗時接近基準 8-9 秒,零上下文切換。
CREATE OR REPLACE FUNCTION f_extract_birthday_udf (...) RETURN VARCHAR2
PRAGMA UDF; -- 告知編譯器優化 SQL 調用路徑
IS ...
實測可降低 30%-50% 開銷,但仍慢於純 SQL。
CREATE OR REPLACE FUNCTION f_extract_birthday_det (...)
RETURN VARCHAR2 DETERMINISTIC RESULT_CACHE IS ...
同一證件號重複出現時極快,但首次調用仍有切換開銷。
PRAGMA UDF、DETERMINISTIC、RESULT_CACHE 三大利器。INSERT /*+ APPEND */ ... SELECT 純SQL邏輯 直載目標表。