想像一下,你寫了一個超複雜的 function 或 procedure。你已經看到你的資料庫跑得超順,結果突然——砰!——資料怪怪的、查詢變慢,老闆開始緊張。這時候,除錯就要上場啦。
PL/pgSQL 的除錯主要是為了:
- 找出邏輯錯誤,比如 function 回傳的東西跟你預期的不一樣。
- 搞懂輸入資料有問題。因為有時候資料庫的使用者不只會輸入資料,還會...輸入一些完全看不懂的東西!
- 解決效能問題。畢竟趕工寫出來的 code,跑起來可能像在沙漠裡找 Wi-Fi 的烏龜一樣慢。
認真說,除錯不只是找 bug 跟修 bug。這也是讓你的 code 更快、更有效率、更好讀的方法。
PL/pgSQL 除錯的基本方法
PL/pgSQL 的除錯有幾種方式。我們一個一個來看。
- 用 PostgreSQL 內建工具
PostgreSQL 有幾個內建的診斷功能,包括 logging function(RAISE NOTICE 跟 RAISE EXCEPTION),還有查詢執行計畫分析(EXPLAIN ANALYZE)。這些工具可以幫你搞懂 function 裡面到底發生什麼事。
- 用
RAISE NOTICE來 logging
RAISE NOTICE 就像你的好朋友,想知道 function 裡面資料怎麼流動、哪個階段出問題、或是檢查變數值,都靠它。跟 RAISE EXCEPTION 不一樣,它不會中斷 function 執行。比如說,你可以在每個階段都 print 出變數內容。
DO $$
DECLARE
counter INT := 0;
BEGIN
FOR counter IN 1..5 LOOP
RAISE NOTICE '目前計數器的值: %', counter;
END LOOP;
END $$;
這段 code 會 print 出 counter 從 1 到 5 的值。超簡單但超實用,除錯必備!
- 用外部工具
PL/pgSQL 除錯也可以靠像 pgAdmin 這種工具(有 GUI 介面)。你可以設 breakpoint、即時看變數值。如果你喜歡視覺化的工具,pgAdmin 絕對是你的好夥伴。
除錯的步驟
開始除錯 function 或 procedure 時,照著一定的順序來很重要。每個步驟我們都來細講一下:
- 分析輸入資料
第一步就是搞清楚輸入資料。要確定 function 拿到的資料沒有錯誤或奇怪的值。比如說,你可以用 RAISE NOTICE 檢查所有輸入參數:
CREATE FUNCTION check_input(x INTEGER) RETURNS VOID AS $$
BEGIN
IF x IS NULL THEN
RAISE EXCEPTION '輸入值不能是 NULL!';
END IF;
RAISE NOTICE '輸入值: %', x;
END;
$$ LANGUAGE plpgsql;
這個例子就是提醒使用者輸入資料有問題時要注意。
- 檢查每個階段的執行情況
把 function 拆成幾個邏輯區塊,在關鍵點加上 RAISE NOTICE。這樣你就能知道哪裡出錯。
CREATE FUNCTION calculate_discount(price NUMERIC, discount NUMERIC) RETURNS NUMERIC AS $$
BEGIN
RAISE NOTICE 'Function 開始: 價格 %, 折扣 %', price, discount;
IF price <= 0 THEN
RAISE EXCEPTION '價格不能是負的或等於零!';
END IF;
IF discount < 0 OR discount > 100 THEN
RAISE EXCEPTION '折扣必須在 0 到 100 之間!';
END IF;
RETURN price - (price * discount / 100);
END;
$$ LANGUAGE plpgsql;
這裡每個除錯階段都會 print 出有用的狀態訊息。
- 優化和解決問題
- 找到 bug 之後就修掉。如果是效能問題,用像
EXPLAIN ANALYZE這種分析工具來優化查詢。
除錯技能的實戰應用
來看個實際例子:我們有個 function 會新增一筆資料到資料表,然後回傳產生的 id。看起來很簡單,但有時 function 會出錯,我們想知道為什麼。
原始 function:
CREATE FUNCTION add_student(name TEXT, age INTEGER) RETURNS INTEGER AS $$
DECLARE
new_id INTEGER;
BEGIN
INSERT INTO students (name, age) VALUES (name, age) RETURNING id INTO new_id;
RETURN new_id;
END;
$$ LANGUAGE plpgsql;
如果你用錯誤的資料呼叫這個 function,比如 age < 0,它就會出錯。我們來用除錯工具把它變更好。
加上 logging 的進階 function:
CREATE FUNCTION add_student(name TEXT, age INTEGER) RETURNS INTEGER AS $$
DECLARE
new_id INTEGER;
BEGIN
-- logging 輸入資料
RAISE NOTICE '新增學生: 名字 %, 年齡 %', name, age;
-- 檢查年齡正確性
IF age < 0 THEN
RAISE EXCEPTION '年齡不能是負的!';
END IF;
-- 新增學生並回傳 ID
INSERT INTO students (name, age) VALUES (name, age) RETURNING id INTO new_id;
-- logging 成功訊息
RAISE NOTICE '學生已新增,ID %', new_id;
RETURN new_id;
END;
$$ LANGUAGE plpgsql;
現在如果出錯,你就能靠 RAISE NOTICE 的訊息馬上知道哪裡有問題。
最後給你的小建議
- 記得把不需要的 logging 拿掉。
RAISE NOTICE超好用,但如果一直留在 production,log 會爆炸。 - 分段寫 code。 如果 function 太複雜,就拆成幾個小 function。這樣除錯超輕鬆。
- 多練習。 你寫 code 跟除錯越多,找 bug 跟修 bug 就越快。
除錯就像玩偵探遊戲,只是你拿的是 SQL 查詢跟 PL/pgSQL 邏輯,不是放大鏡。這種功力是靠經驗累積的,每修一個 bug,你就更強一點!
GO TO FULL VERSION