刚接触MySQL的开发者经常把用户定义变量和局部变量混为一谈,写出来的SQL有时候能跑,有时候又莫名报错,问题多半出在对这两类变量的作用域和生命周期理解不清。用户定义变量以@符号开头,属于会话级别的变量;局部变量则必须用DECLARE关键字声明,只在存储程序内部的代码块中有效。这篇文章会从基本语法入手,逐一分析两种变量的特点、赋值方式和使用场景,并给出一些实际开发中的避坑建议。

用户定义变量:会话级的轻量工具
用户定义变量(User-Defined Variable)是MySQL中最早支持的一种变量形式,它的名字必须以@开头,例如@total、@row_num。这类变量最大的特点是生命周期与当前会话绑定,也就是说你在一个数据库连接里给@total赋了值,只要连接不断开,后续的查询都能读到这个值,而其他连接是看不到的。
用户定义变量不需要事先声明,直接赋值即可使用,赋值推荐使用SET语句,也可以在SELECT语句中用:=操作符完成。看下面两个等价的写法:
-- 方式一:使用SET赋值 SET @total = 100; SET @name := 'mysql'; -- 方式二:在SELECT中赋值 SELECT @count := COUNT(*) FROM employees; -- 查看变量的值 SELECT @total, @name, @count;
需要注意的是,MySQL 8.0之前的版本允许在普通SELECT语句中直接用:=给变量赋值,同时把赋值结果返回出来,这在统计行号、累加计算时非常方便。不过在MySQL 8.0及之后的版本中,官方已经不推荐这种依赖执行顺序的用法,行为可能变得不可预期,遇到类似需求时更稳妥的做法是改用窗口函数。
用户定义变量还有一个特性:未经赋值直接引用时,它的值是NULL,不会报错。这一点有利有弊,好处是使用起来宽松,坏处是拼写错误的变量名不会被发现,排查问题时要多留个心眼。
局部变量:存储程序内部的专用变量
局部变量(Local Variable)必须用DECLARE语句显式声明,而且DECLARE必须写在BEGIN...END代码块的开头位置,放在其他语句之前。局部变量只存在于存储过程、存储函数、触发器、事件这些存储程序中,普通的命令行SQL是无法使用局部变量的。
声明时可以直接指定数据类型和默认值,赋值则用SET语句,写法上省略@符号:
DELIMITER //
CREATE PROCEDURE calc_bonus(IN emp_salary DECIMAL(10,2))
BEGIN
-- 声明局部变量,必须放在代码块最前面
DECLARE bonus DECIMAL(10,2) DEFAULT 0;
DECLARE rate DECIMAL(4,2) DEFAULT 0.10;
-- 赋值和使用
SET bonus = emp_salary * rate;
SELECT bonus AS 计算结果;
END //
DELIMITER ;
CALL calc_bonus(8000);
局部变量的作用域严格限定在声明它的BEGIN...END块内。如果存储过程里存在嵌套的代码块,内层代码块可以访问外层声明的变量,但外层无法访问内层声明的变量,这一点和大多数编程语言的块级作用域规则类似。
与用户定义变量相比,局部变量是强类型的,声明时就确定了数据类型,赋值时如果类型不匹配MySQL会做隐式转换,转换失败会报错,这能在编译阶段就暴露一部分问题,代码的严谨性更好。
两类变量的核心区别与对比
把两种变量放在一起对比,差异就非常明显了。下面从几个维度做个梳理:
- 命名形式:用户定义变量以@开头,局部变量没有@前缀,两者名字可以相同互不干扰。
- 声明要求:用户定义变量无需声明直接使用,局部变量必须先DECLARE声明并指定类型。
- 作用范围:用户定义变量在整个会话中有效,局部变量只在所在的BEGIN...END块内有效。
- 使用场景:用户定义变量可以在普通SQL中使用,局部变量只能用于存储程序内部。
- 初始化行为:用户定义变量未赋值时为NULL,局部变量未指定DEFAULT时初始值也为NULL,但局部变量有明确的类型约束。
还有一个容易忽略的细节:在存储过程内部,如果同时存在同名的用户定义变量和局部变量,写法上不会混淆,因为前者带@后者不带,MySQL会自动区分。但如果在触发器或存储函数中误把局部变量名写成了列名,就可能引发难以察觉的逻辑错误,所以命名时建议给变量加上统一前缀,比如v_或l_,与表字段区分开。
实际使用中的常见坑与最佳实践
第一个常见的坑是在SELECT语句中用=给用户定义变量赋值。在SET语句中=和:=效果一样,但在SELECT中=是比较运算符,必须用:=才能赋值,写错了语法上可能不报错,但结果完全不对。
-- 错误示范:SELECT中的=是比较,不是赋值 SELECT @rank = 1; -- 返回的是比较结果0或1 -- 正确写法 SELECT @rank := 1; -- 把1赋值给@rank
第二个坑是依赖变量赋值的执行顺序。比如在一条语句里既给变量赋值又在别处引用它,MySQL不保证各处表达式的求值顺序,结果可能因版本或优化器策略而不同。稳妥的做法是把赋值和引用拆到多条语句中完成。
第三,局部变量的DECLARE位置必须严格遵守规则,一旦放在IF、SET等其他语句之后,会直接抛出语法错误。如果需要条件声明或中途声明变量,可以额外嵌套一层BEGIN...END块来解决。
DELIMITER //
CREATE PROCEDURE demo_scope()
BEGIN
DECLARE v_outer INT DEFAULT 1;
BEGIN
-- 内层代码块声明的变量,外层不可见
DECLARE v_inner INT DEFAULT 2;
SET v_outer = v_outer + v_inner; -- 内层可以访问外层变量
END;
-- SET v_outer = v_inner; -- 这里会报错,v_inner已不可见
SELECT v_outer;
END //
DELIMITER ;
最后总结一下选用原则:需要在多条独立SQL语句之间传递临时结果,或者想在客户端脚本里做简单的状态记录,用用户定义变量;在存储过程、函数内部承载业务逻辑中的中间计算,一律用局部变量,它类型明确、作用域清晰,代码可读性和可维护性都更好。分清这两类变量,写出来的SQL和存储过程才会更干净、更不容易出问题。
MySQL用户定义变量局部变量存储过程变量修改时间:2026-09-10 19:48:38