变量
导学
存储过程和函数中需要使用变量来临时存储数据、控制循环、传递结果。MySQL 提供了三种变量:局部变量、用户变量和系统变量。本节讲清它们的区别和使用场景。
定义
变量:在 SQL 执行过程中用于临时存储数据的命名内存位置。MySQL 支持局部变量(过程/函数内)、用户变量(会话级)和系统变量(服务器配置)。
局部变量
在存储过程或函数内部使用,作用域限于 BEGIN...END 块。
完整示例:局部变量声明与赋值
当前数据状态
首先建立示例表并插入数据:
CREATE DATABASE IF NOT EXISTS school;
USE school;
DROP TABLE IF EXISTS employees;
CREATE TABLE employees (
emp_id INT PRIMARY KEY AUTO_INCREMENT,
emp_name VARCHAR(20),
dept VARCHAR(20),
score DECIMAL(5,2)
);
INSERT INTO employees (emp_name, dept, score) VALUES
('大翔', '技术部', 100),
('白歌', '技术部', NULL);
当前 employees 表数据:
| emp_id | emp_name | dept | score |
|---|---|---|---|
| 1 | 大翔 | 技术部 | 100 |
| 2 | 白歌 | 技术部 | NULL |
操作语句
创建使用局部变量的存储过程:
DELIMITER //
CREATE PROCEDURE CalcSum(IN a INT, IN b INT, OUT result INT)
BEGIN
DECLARE temp INT DEFAULT 0; -- 声明局部变量,默认值为 0
SET temp = a + b;
SET result = temp;
END //
DELIMITER ;
-- 调用
SET @res = 0;
CALL CalcSum(10, 20, @res);
SELECT @res AS result;
操作后的结果
返回:
| result |
|---|
| 30 |
结果解读
DECLARE temp INT DEFAULT 0;声明局部变量temp,仅在BEGIN...END块内有效SET temp = a + b;使用SET为局部变量赋值- 局部变量不能在该块外部访问,过程执行完毕后自动释放
完整示例:局部变量通过 SELECT INTO 赋值
当前数据状态
基于 employees 表:
| emp_id | emp_name | dept | score |
|---|---|---|---|
| 1 | 大翔 | 技术部 | 100 |
| 2 | 白歌 | 技术部 | NULL |
操作语句
创建使用 SELECT ... INTO 的存储过程:
DELIMITER //
CREATE PROCEDURE GetStats()
BEGIN
DECLARE v_count INT DEFAULT 0;
DECLARE v_avg DECIMAL(5,2);
DECLARE v_max DECIMAL(5,2);
SELECT COUNT(*) INTO v_count FROM employees;
SELECT AVG(score) INTO v_avg FROM employees;
SELECT MAX(score) INTO v_max FROM employees;
SELECT v_count AS 总人数, v_avg AS 平均分, v_max AS 最高分;
END //
DELIMITER ;
-- 调用
CALL GetStats();
操作后的结果
返回:
| 总人数 | 平均分 | 最高分 |
|---|---|---|
| 2 | 100.0000 | 100 |
结果解读
SELECT COUNT(*) INTO v_count FROM employees;将查询结果赋值给局部变量INTO子句必须放在 SELECT 列表的末尾,且查询应只返回一行- 局部变量
v_count、v_avg、v_max仅在过程内部有效
声明与赋值的三种方式
DECLARE var_name datatype [DEFAULT value]; -- 声明
SET var_name = expr; -- SET 赋值
SELECT col INTO var_name FROM table; -- SELECT INTO 赋值
用户变量
以 @ 开头,作用域为当前会话,可在任意 SQL 中使用。
完整示例:用户变量的基本使用
当前数据状态
基于 employees 表:
| emp_id | emp_name | dept | score |
|---|---|---|---|
| 1 | 大翔 | 技术部 | 100 |
| 2 | 白歌 | 技术部 | NULL |
操作语句
-- 直接赋值
SET @name = '大翔';
SET @age = 20, @score = 90.5;
SELECT @name, @age, @score;
-- 在查询中赋值
SELECT COUNT(*) INTO @total FROM employees;
SELECT @total;
操作后的结果
第一次查询返回:
| @name | @age | @score |
|---|---|---|
| 大翔 | 20 | 90.5 |
第二次查询返回:
| @total |
|---|
| 2 |
结果解读
- 用户变量以
@开头,在当前会话内有效 - 可以一次设置多个变量:
SET @a = 1, @b = 2; - 用户变量未初始化时值为
NULL,不会报错
完整示例:用户变量跨查询传递
当前数据状态
基于 employees 表:
| emp_id | emp_name | dept | score |
|---|---|---|---|
| 1 | 大翔 | 技术部 | 100 |
| 2 | 白歌 | 技术部 | NULL |
操作语句
-- 第一步:获取总数
SELECT COUNT(*) INTO @total FROM employees;
SELECT @total AS 总人数;
-- 第二步:用它做分页计算
SET @page_size = 1;
SET @total_pages = CEIL(@total / @page_size);
SELECT @total_pages AS 总页数;
-- 第三步:计算最后一页的记录数
SET @last_page_size = @total - (@total_pages - 1) * @page_size;
SELECT @last_page_size AS 最后一页记录数;
操作后的结果
第一步返回:
| 总人数 |
|---|
| 2 |
第二步返回:
| 总页数 |
|---|
| 2 |
第三步返回:
| 最后一页记录数 |
|---|
| 1 |
结果解读
@total在第一步查询中被赋值,后续步骤可以继续使用- 用户变量的会话级特性使其非常适合跨查询传递中间结果
@total_pages通过CEIL(@total / @page_size)计算,2 条记录每页 1 条需要 2 页@last_page_size计算最后一页的实际记录数:2 - (2-1)*1 = 1
完整示例:避免变量未初始化的陷阱
当前数据状态
基于 employees 表:
| emp_id | emp_name | dept | score |
|---|---|---|---|
| 1 | 大翔 | 技术部 | 100 |
| 2 | 白歌 | 技术部 | NULL |
操作语句
-- 先初始化变量
SET @sum = 0;
-- 使用 := 在 SELECT 中累加
SELECT emp_id, score, @sum := @sum + IFNULL(score, 0) AS running_total
FROM employees
ORDER BY emp_id;
-- 查看最终累加值
SELECT @sum AS 总分;
操作后的结果
累加查询返回:
| emp_id | score | running_total |
|---|---|---|
| 1 | 100 | 100 |
| 2 | NULL | 100 |
最终查询返回:
| 总分 |
|---|
| 100 |
结果解读
:=是赋值运算符,用于在SELECT中赋值=在SET中是赋值,在SELECT中是比较- 如果不先初始化
@sum,第一次使用时其值为NULL,NULL + 85仍然是NULL
对比错误写法:
-- 不初始化,直接使用
SELECT emp_id, score, @sum2 := @sum2 + score AS running_total
FROM employees
ORDER BY emp_id;
错误结果:
| emp_id | score | running_total |
|---|---|---|
| 1 | 100 | NULL |
| 2 | NULL | NULL |
因为 @sum2 未初始化,值为 NULL,导致所有累加结果都是 NULL。
系统变量
以 @@ 开头,分为全局(@@global.var)和会话(@@session.var)两个级别。
完整示例:查看和设置系统变量
操作语句
-- 查看系统变量
SELECT @@global.max_connections;
SELECT @@session.sql_mode;
SELECT @@sql_mode; -- 默认查看会话级
-- 设置会话变量
SET SESSION sql_mode = 'STRICT_TRANS_TABLES';
SELECT @@sql_mode;
-- 设置全局变量(需 SUPER 权限)
-- SET GLOBAL max_connections = 500;
操作后的结果
查看 @@global.max_connections 返回(具体值因配置而异):
| @@global.max_connections |
|---|
| 151 |
查看 @@session.sql_mode 返回(具体值因配置而异):
| @@session.sql_mode |
|---|
| ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,... |
设置会话变量后再查看:
| @@sql_mode |
|---|
| STRICT_TRANS_TABLES |
结果解读
@@global.xxx查看/设置全局变量,影响所有连接@@session.xxx或@@xxx查看/设置会话变量,只影响当前连接- 设置全局变量需要
SUPER权限,且重启后可能丢失(除非写入配置文件)
常用系统变量
| 变量 | 含义 |
|---|---|
@@autocommit | 是否自动提交事务 |
@@sql_mode | SQL 模式(如 ONLY_FULL_GROUP_BY) |
@@max_connections | 最大连接数 |
@@innodb_buffer_pool_size | InnoDB 缓冲池大小 |
@@character_set_database | 数据库默认字符集 |
完整示例:局部变量与用户变量的对比
当前数据状态
基于 employees 表:
| emp_id | emp_name | dept | score |
|---|---|---|---|
| 1 | 大翔 | 技术部 | 100 |
| 2 | 白歌 | 技术部 | NULL |
操作语句
-- 用户变量在过程外定义
SET @outer_var = '用户变量值';
DELIMITER //
CREATE PROCEDURE VarScopeDemo()
BEGIN
DECLARE local_var VARCHAR(50); -- 局部变量
SET local_var = '局部变量值';
-- 局部变量和用户变量都可以在过程内使用
SELECT local_var AS 局部变量, @outer_var AS 用户变量;
END //
DELIMITER ;
CALL VarScopeDemo();
-- 过程外只能访问用户变量
SELECT @outer_var AS 过程外用户变量;
-- 过程外不能访问局部变量,下面这句会报错
-- SELECT local_var; -- ERROR 1054 (42S22): Unknown column 'local_var' in 'field list'
操作后的结果
过程内查询返回:
| 局部变量 | 用户变量 |
|---|---|
| 局部变量值 | 用户变量值 |
过程外查询返回:
| 过程外用户变量 |
|---|
| 用户变量值 |
结果解读
- 局部变量用
DECLARE声明,作用域在BEGIN...END内,过程外不可见 - 用户变量用
@var表示,作用域是整个会话,过程内外都可见 - 过程中应优先使用局部变量,避免命名冲突和意外副作用
常见误区
| 误区 | 正解 |
|---|---|
| 局部变量和用户变量可以混用 | 语法不同(DECLARE vs SET @),作用域不同。过程中优先用局部变量。 |
= 和 := 没有区别 | 在 SET 中两者都是赋值;在 SELECT 中 = 是比较,:= 是赋值。 |
| 用户变量会随连接断开而保留 | 不会。用户变量是会话级的,连接断开即消失。 |
面试考点
Q:局部变量和用户变量的区别?
局部变量用
DECLARE声明,作用域在BEGIN...END内;用户变量用@var表示,作用域是整个会话。过程中应优先使用局部变量。
Q:SELECT @a = 1 和 SELECT @a := 1 的区别?
=在SELECT中是比较运算符,SELECT @a = 1是比较 @a 是否等于 1,返回布尔结果;:=是赋值运算符,SELECT @a := 1将 1 赋给 @a。
Q:如何查看当前 SQL Mode?
SELECT @@sql_mode;或SHOW VARIABLES LIKE 'sql_mode';。
小结
- 局部变量用于过程/函数内部,用
DECLARE声明 - 用户变量以
@开头,会话级生命周期 - 系统变量以
@@开头,分为全局和会话级别 SELECT中赋值用:=,避免与比较运算符=混淆
下一章引子:有了变量,还需要流程控制语句来实现条件判断和循环——IF、CASE、LOOP、WHILE。