DECLARE必须紧接BEGIN之后,否则MySQL解析器报错;嵌套块中同名变量自动遮蔽;PREPARE不识别DECLARE变量,需转为@变量;同名@变量与局部变量互不影响。

DECLARE必须紧接BEGIN之后,否则直接报错
MySQL解析器强制要求所有DECLARE语句(包括变量、游标、HANDLER)必须出现在BEGIN之后、任何可执行语句之前。一旦中间夹了SELECT、SET或IF,立刻触发ERROR 1064 (42000)或ERROR 1337 (42000)。
常见错误写法:
CREATE PROCEDURE bad_proc() BEGIN SELECT 1; -- ❌ 这行导致后续DECLARE非法 DECLARE x INT DEFAULT 0; END;
正确顺序只能是:
-
DECLARE全部堆在最前面 - 然后才是
SET、SELECT INTO、OPEN、IF等可执行语句 - 嵌套块也得各自遵守——每个
BEGIN ... END内部都要重排一次
嵌套块中同名变量会静默遮蔽,不报错也不警告
内层BEGIN ... END里声明DECLARE x INT,不会和外层同名变量冲突,而是自动遮蔽(shadow)。运行时读写的是当前块的副本,但调试时很难察觉。
典型现象:
- 外层
DECLARE x INT DEFAULT 10;,内层DECLARE x INT DEFAULT 20; - 内层
SELECT x;返回20,外层SELECT x;仍返回10 - 若你在循环里反复进/出嵌套块,值看似“重置”,实则是每次都在操作新变量
避免方式:给局部变量加前缀,比如l_counter、l_user_id;参数用p_id;用户变量坚持用@batch_start_time这种带业务含义的命名。
PREPARE动态SQL无法直接引用DECLARE变量
PREPARE只认字面字符串和@用户变量,完全无视DECLARE声明的局部变量。硬塞进去会变成NULL或触发Unknown column 'v_id' in 'field list'这类错误。
错误尝试:
DECLARE v_id INT DEFAULT 123; PREPARE stmt FROM 'SELECT * FROM t WHERE id = ?'; EXECUTE stmt USING v_id; -- ❌ v_id不被识别
可行方案:
- 先赋值给
@变量:SET @v_id = v_id;,再EXECUTE stmt USING @v_id; - 用
CONCAT()拼接SQL,但字符串值必须套QUOTE(v_str)防注入 - 拼接后检查长度:
SELECT CHAR_LENGTH(@sql);,避免超max_allowed_packet被截断
@用户变量与DECLARE同名时优先级陷阱
当存储过程里同时存在DECLARE v INT和SET @v = 5,后续写SET v = 100修改的只是局部变量,@v仍是5——但你可能以为两者联动。
更隐蔽的问题:
- 循环中用
@sum := @sum + val累加,退出后SELECT @sum却返回旧值 - 原因往往是中间某处
DECLARE sum INT意外遮蔽了@sum,而SELECT @sum查的是未被修改的那个 -
SELECT v := 100这种写法默认操作局部变量,不是赋值给@v,极易埋雷
局部变量生命周期只到END就销毁,但遮蔽逻辑和@变量残留效应,在多层嵌套+动态SQL混合场景下才集中爆发。这时候光看单个DECLARE位置没用,得顺着块结构画作用域树才能理清。


















