mysql存储过程要求declare变量、游标、处理器必须紧接begin之后且位于所有可执行语句之前,否则报错;局部变量仅在声明它的begin...end块内有效,嵌套块需独立声明以隔离作用域,用户变量@与系统变量@@全局可见,不可混用。

DECLARE变量必须在BEGIN后立即声明
MySQL要求所有DECLARE语句必须紧接在BEGIN之后、任何可执行语句之前,否则报错ERROR 1337 (42000): Variable or condition declaration after cursor or handler declaration。这不是风格问题,是语法硬性限制。
常见错误写法:
CREATE PROCEDURE bad_proc() BEGIN SELECT 1; -- 错了!这条先执行,后面DECLARE就非法 DECLARE x INT DEFAULT 0; END;
正确顺序只能是:
-
DECLARE变量(含CURSOR、HANDLER)全部放在最前面 - 然后才是
SET、SELECT ... INTO、IF、游标操作等可执行语句 - 嵌套块也遵循同一规则:每个
BEGIN ... END内部都要重新按此顺序组织
嵌套BEGIN END块是隔离作用域的唯一可靠方式
MySQL没有let或const作用域机制,局部变量只在它被声明的BEGIN ... END块内有效。但很多人误以为“缩进”或“注释”能分隔作用域——不行,只有显式BEGIN ... END才起作用。
典型混乱场景:两个游标共用同名CONTINUE HANDLER FOR NOT FOUND,子游标结束触发异常后,上层游标NOT FOUND标志也被置为1,导致提前退出。
解决方法是给每个游标逻辑包一层独立块:
BLOCK1: BEGIN
DECLARE done1 INT DEFAULT 0;
DECLARE cur1 CURSOR FOR SELECT id FROM t1;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done1 = 1;
OPEN cur1;
read_loop1: LOOP
FETCH cur1 INTO @id1;
IF done1 THEN LEAVE read_loop1; END IF;
BLOCK2: BEGIN -- 关键:新块,新作用域
DECLARE done2 INT DEFAULT 0;
DECLARE cur2 CURSOR FOR SELECT name FROM t2 WHERE parent_id = @id1;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done2 = 1; -- 不影响done1
OPEN cur2;
...
END BLOCK2;
END LOOP;
END BLOCK1;
注意:BLOCK1:和BLOCK2:不是必须命名,但命名有助于调试和避免LEAVE目标歧义。
局部变量与用户变量(@)、系统变量(@@)混用极易踩坑
新手常把DECLARE x INT和SET @x = 1当一回事,其实三者完全不互通:
-
DECLARE x:纯局部,出块即销毁,不能跨BEGIN END -
@x(用户变量):会话级,当前连接里所有存储过程、函数、查询都可见,修改即全局生效 -
@@x(系统变量):分GLOBAL和SESSION,如@@sql_mode,与业务逻辑变量无关
一个真实翻车案例:
CREATE PROCEDURE p()
BEGIN
DECLARE id INT DEFAULT 100;
SET @id = 200;
SELECT id, @id; -- 输出 100, 200
BEGIN
DECLARE id INT DEFAULT 300; -- 遮蔽外层id
SELECT id, @id; -- 输出 300, 200(@id仍是会话级)
END;
SELECT id, @id; -- 输出 100, 200(外层id恢复,@id没变过)
END;
所以别用@传值或临时存中间结果——除非你真需要跨过程通信;日常逻辑一律用DECLARE,并加前缀如l_id、v_count来强化语义。
MySQL 8.0+的BLOCK语法能减少嵌套混乱,但不改变根本规则
MySQL 8.0引入了BLOCK关键字(如my_block: BLOCK ... END BLOCK my_block;),看起来更清晰,但它只是BEGIN ... END的语法糖,**不改变变量作用域规则**。
也就是说:BLOCK内仍需遵守“声明前置”,仍不能访问外层BLOCK的DECLARE变量,@变量依然全局可见。
真正值得升级到8.0+的理由是:BLOCK让嵌套结构更易读、LEAVE block_name跳转更安全、配合GET DIAGNOSTICS做错误处理更可控。但如果你还在用5.7,老老实实用带标签的BEGIN ... END一样能写出健壮逻辑——关键是意识到位,不是语法炫技。
最容易被忽略的一点:哪怕你用了10层嵌套块,只要有一处DECLARE写在可执行语句之后,整个存储过程创建就会失败,且错误提示非常模糊。务必养成“声明→逻辑”的肌肉记忆。











