MySQL 支援使用者自定義函數 (UDF),可封裝業務邏輯、簡化 SQL、提升複用性。本文涵蓋建立語法、決定性聲明、字元集陷阱、權限控制、以及生產環境避坑指南。
CREATE FUNCTION `function_name` (param1 TYPE, param2 TYPE, ...)
RETURNS RETURN_TYPE
[DETERMINISTIC | NOT DETERMINISTIC]
[READS SQL DATA | MODIFIES SQL DATA | NO SQL | CONTAINS SQL]
[SQL SECURITY {DEFINER | INVOKER}]
[COMMENT 'description']
BEGIN
-- 函數體
DECLARE var_name TYPE DEFAULT value;
-- 邏輯
RETURN result_value;
END;
DELIMITER $$
DROP FUNCTION IF EXISTS `fn_status_to_zh`$$
CREATE FUNCTION `fn_status_to_zh` (`p_status` VARCHAR(20))
RETURNS VARCHAR(50)
DETERMINISTIC
READS SQL DATA
SQL SECURITY INVOKER
COMMENT '將英文狀態碼轉換為中文顯示'
BEGIN
DECLARE `v_result` VARCHAR(50) DEFAULT '未知';
CASE `p_status`
WHEN 'online' THEN SET `v_result` = '上線';
WHEN 'offline' THEN SET `v_result` = '下線';
WHEN 'pending' THEN SET `v_result` = '待審';
WHEN 'banned' THEN SET `v_result` = '封禁';
ELSE SET `v_result` = '未知';
END CASE;
RETURN `v_result`;
END$$
DELIMITER ;
-- 測試
SELECT `fn_status_to_zh`('online'); -- 上線
SELECT `fn_status_to_zh`('unknown'); -- 未知
| 子句 | 選項 | 說明與建議 |
|---|---|---|
DETERMINISTIC | DETERMINISTIC / NOT DETERMINISTIC | 相同輸入必產生相同輸出。 ✅ 純計算/查表函數標記 DETERMINISTIC,優化器可快取結果。❌ 涉及 NOW()、RAND()、查詢資料表則標記 NOT DETERMINISTIC。 |
| 資料存取 | READS SQL DATA / MODIFIES SQL DATA / NO SQL / CONTAINS SQL | READS SQL DATA:含 SELECT(預設)。MODIFIES SQL DATA:含 INSERT/UPDATE/DELETE(函數中**極不建議**寫入操作)。NO SQL:純計算無 SQL。CONTAINS SQL:含 SET/DECLARE 等非讀寫 SQL。 |
SQL SECURITY | DEFINER / INVOKER | DEFINER:以建立者權限執行(預設),需建立者有被查詢物件權限。INVOKER:以呼叫者權限執行,**推薦**,符合最小權限原則。 |
**現象**:函數參數/變數/返回值出現亂碼,特別是中文參數傳入。
**原因**:函數預設使用資料庫預設字元集,若資料庫是 latin1 而連線是 utf8mb4,參數會被錯誤轉碼。
**解決**:
-- 1. 顯式指定參數/返回值字元集
CREATE FUNCTION `fn_test` (`p_str` VARCHAR(100) CHARACTER SET utf8mb4)
RETURNS VARCHAR(200) CHARACTER SET utf8mb4
...
-- 2. 變數宣告指定字元集
DECLARE `v_str` VARCHAR(100) CHARACTER SET utf8mb4 DEFAULT '';
-- 3. 函數建立時指定整體字元集(8.0+)
CREATE FUNCTION `fn_test` (...) RETURNS ... CHARACTER SET utf8mb4 ...
-- 授予建立函數權限
GRANT CREATE ROUTINE ON `mydb`.* TO 'dev_user'@'%';
-- 授予執行函數權限
GRANT EXECUTE ON `mydb`.* TO 'app_user'@'%';
-- 查看函數定義
SHOW CREATE FUNCTION `fn_status_to_zh`;
SELECT * FROM information_schema.ROUTINES WHERE ROUTINE_NAME = 'fn_status_to_zh';
啟用 Binlog (log_bin=ON) 時,建立函數需滿足下列任一條件,否則報錯 ERROR 1418 (HY000):
DETERMINISTIC 且 READS SQL DATA 或 NO SQLlog_bin_trust_function_creators = ON-- 線上暫時開啟(重啟失效)
SET GLOBAL log_bin_trust_function_creators = 1;
-- 永久生效 (my.cnf)
[mysqld]
log_bin_trust_function_creators = 1
| 場景 | 建議 |
|---|---|
| 高頻呼叫的純計算邏輯 | 優先考慮應用層實現(Java/Go/Python),避免網路往返與 SQL 解析開銷 |
| 複雜查詢封裝 | 改用 View (視圖) 或 Stored Procedure (儲存過程),函數不支援返回結果集 |
| 決定性函數 | 務必加 DETERMINISTIC,啟用查詢快取(5.7)或優化器常數摺疊 |
| 函數內查詢大表 | ❌ 禁用,會導致 N+1 問題。改用 JOIN 或應用層批次查詢 |
| 命名規範 | 前綴 fn_,小寫下劃線,如 fn_calc_shipping_fee |
| 測試 | 建立單元測試資料,驗證邊界值、NULL、異常輸入 |
| 特性 | Function | Procedure | View |
|---|---|---|---|
| 返回值 | 單一標量值 | OUT 參數 / 結果集 | 結果集 (虛擬表) |
| 可在 SELECT 中呼叫 | ✅ | ❌ | ✅ |
| 可執行 DML | ❌ (不建議) | ✅ | ❌ |
| 事務控制 | ❌ | ✅ | ❌ |
| 適用場景 | 純計算、格式轉換、查單值 | 複雜業務流程、批次處理 | 簡化複雜 JOIN、權限控制 |