MySQL 擁有兩種截然不同的變量體系:用戶定義變量 (User-Defined Variables,@var) 與 存儲程序局部變量 (Local Variables,var)。混淆兩者會導致難以排查的 Bug。本文從作用域、生命週期、初始化、類型系統等維度深度對比。
| 特性 | 用戶變量 @var | 局部變量 var |
|---|---|---|
| 前綴 | 必須 @ 開頭 | 無前綴 |
| 聲明 | 無需聲明,直接賦值 | DECLARE var type [DEFAULT val] |
| 作用域 | 整個會話 (Session) | BEGIN...END 區塊 |
| 生命週期 | 連線斷開銷毀 | 區塊結束銷毀 |
| 初始值 | NULL (未賦值前) | DEFAULT 指定或 NULL |
| 類型系統 | 鬆散類型,自動轉換 | 強類型,聲明時固定 |
| 賦值語法 | SET @var = expr / SELECT @var := expr | SET var = expr / SELECT col INTO var |
| 可見性 | 同一連線所有存儲程序、觸發器、SQL | 僅當前程序區塊內部 |
| 持久化 | 跨存儲過程調用保持值 | 每次調用重新初始化 |
-- 建立測試過程 DELIMITER $$ CREATE PROCEDURE sp_var_demo() BEGIN DECLARE v_local INT DEFAULT 1; -- 局部變量 SET v_local = v_local + 1; SET @v_session = @v_session + 1; -- 用戶變量 SELECT v_local AS local_var, @v_session AS session_var; END$$ DELIMITER ; -- 初始化用戶變量 SET @v_session = 1; -- 首次調用 CALL sp_var_demo(); -- 結果: local_var=2, session_var=2 -- 二次調用 (局部變量重置,用戶變量保持) CALL sp_var_demo(); -- 結果: local_var=2, session_var=3 -- 三次調用 CALL sp_var_demo(); -- 結果: local_var=2, session_var=4
-- 錯誤:若 @v 為 NULL,結果為 NULL
SELECT CONCAT('value=', @v);
-- 正確:使用 IFNULL/COALESCE
SELECT CONCAT('value=', IFNULL(@v, 'NULL'));
SET @x = '123'; -- 字符串 SET @x = @x + 1; -- 自動轉為數字 124 SET @x = CONCAT(@x, 'abc'); -- 又變字符串 '124abc'
建議:關鍵邏輯顯式 CAST(@x AS UNSIGNED) 或改用局部變量。
連接池復用連線時,上一個請求殘留的 @var 會影響下一個請求。解決:存儲過程入口統一 SET @var = NULL 初始化,或改用局部變量。
DECLARE v_id INT; SELECT id INTO v_id FROM t WHERE ...; -- 若表有 id 欄位,無歧義 -- 若表也有 v_id 欄位,需加限定符 SELECT t.id INTO v_id FROM t ...;
除了上述兩類,MySQL 還有 系統變量:
@@global.sort_buffer_size:全局配置,需 SUPER 權限修改,新連線生效。@@session.sql_mode:會話級,當前連線立即生效,SET SESSION sql_mode = 'STRICT_TRANS_TABLES'。v_ 開頭,用戶變量 @v_ 開頭,系統變量直接 @@。搞清三類變量的邊界,是編寫穩健、可維護 MySQL 存儲代碼的基礎功力。