DECLARE必须放在BEGIN块最开头,MySQL要求所有DECLARE语句紧接BEGIN后、任何可执行语句前,否则报ERROR 1337;嵌套块中同名变量会遮蔽外层变量,局部变量作用域限于声明块,@用户变量跨块但存在连接池污染风险。
MySQL 要求所有 DECLARE 语句必须紧接在 BEGIN 后、任何可执行语句(SET、SELECT、IF、WHILE 等)之前,否则会报错 ERROR 1337 (42000): Variable declaration after statement。
常见错误现象:
在 IF 分支里写 DECLARE v_flag INT; → 直接语法错误,MySQL 不允许动态声明。
DECLARE 只能用于定义局部变量,且类型必须显式指定,比如 DECLARE v_total DECIMAL(10,2) DEFAULT 0.0;
BEGIN ... END 块可以各自 DECLARE 同名变量,互不干扰@user_var 类型的用户变量生命周期绑定当前连接,只要连接没断,就能在任意 BEGIN/END、IF、WHILE 中读写。但它不是“安全共享”的替代方案。
使用场景:
循环计数后需在循环外继续用;调试时临时存查出的字段值(如 SELECT @debug := name FROM user LIMIT 1;)。
:=,写成 = 就变成布尔比较,结果恒为 0 或 1
NULL,且不报错 —— 容易掩盖逻辑缺陷@var 可能被下一个请求误读,造成数据污染@ 变量没有隔离性如果存储过程中同时存在 DECLARE v_id INT; 和 SET @v_id = 100;,后续写 SET v_id = 200; 修改的是局部变量,@v_id 完全不变 —— 你可能以为在操作用户变量,实际根本没碰它。
这个优先级规则常导致调试困惑:明明写了 SET @v_id = ...,但后续 SELECT @v_id 还是旧值,问题往往出在同名局部变量遮蔽了 @ 变量。
v_user_id,用户变量用 g_user_id 或 tmp_user_id
SELECT @x; 或 SELECT v_x;,而不是靠上下文推测想把循环里的累计结果传到循环外、或者在多个子过程间传递中间状态?@ 变量看似方便,实则隐患密集。
真正健壮的做法是:
OUT 或 INOUT 参数传递单值,比如 CREATE PROCEDURE calc(IN in_val INT, OUT out_sum INT)
CREATE TEMPORARY TABLE,显式 DROP TEMPORARY TABLE 清理@ 变量 —— 触发器可能在不同上下文中执行,@ 值不可控sql_mode=STRICT_TRANS_TABLES 可帮助捕获未声明变量的误用,但对 @ 变量无效局部变量只活在块里,@ 变量看似自由,实则边界模糊。真正的控制力来自明确的作用域声明和显式的传值路径 —— 越想省事绕开规则,越容易掉进隐式行为的坑里。