MySQL变量、分支、循环、游标与条件处理程序全攻略
作者:bksczm
一、变量
MySQL 中的变量分为三类:系统变量、用户自定义变量、局部变量。
1.1 系统变量
系统变量是 MySQL 服务器内置的变量,用于控制服务器的行为。分为两个级别:
| 级别 | 关键字 | 作用范围 |
|---|---|---|
| 全局变量 | GLOBAL | 对所有会话生效,作为新建会话的初始化模板 |
| 会话变量 | SESSION | 仅对当前连接会话生效,会话结束即销毁 |
全局变量与会话变量的关系
当客户端与服务端握手成功、建立会话时:
- 会话变量从全局变量拷贝一份初始值;
- 之后会话变量独立运行,修改会话变量不影响全局变量;
- 修改全局变量只对之后新建的会话生效,对当前已存在的会话不生效。
一句话总结:全局变量是模板,会话变量是当前会话实际使用的副本。
查看系统变量
方式一:SHOW
SHOW [GLOBAL | SESSION] VARIABLES [LIKE 'pattern'];
-- 查看所有全局变量 SHOW GLOBAL VARIABLES; -- 查看所有会话变量(默认就是 SESSION) SHOW SESSION VARIABLES; SHOW VARIABLES; -- 等价于上面 -- 模糊查询(LIKE 支持 % 通配符) SHOW VARIABLES LIKE 'auto%'; -- 以 auto 开头 SHOW VARIABLES LIKE '%auto%'; -- 包含 auto SHOW VARIABLES LIKE 'autocommit'; -- 精确匹配
不指定
GLOBAL或SESSION时,默认是SESSION。
方式二:SELECT @@
SELECT @@[GLOBAL.|SESSION.]variable_name;
SELECT @@GLOBAL.autocommit; -- 查看全局变量 SELECT @@SESSION.autocommit; -- 查看会话变量 SELECT @@autocommit; -- 默认查看会话变量
@@ 表示这是系统变量,后面跟 . 和变量名。
修改系统变量
SET @@[GLOBAL.|SESSION.]variable_name = new_value; -- 或 SET [GLOBAL | SESSION] variable_name = new_value; -- 修改会话变量(当前会话生效) SET @@SESSION.autocommit = 0; SET autocommit = 0; -- 默认修改会话级别 -- 修改全局变量(需要管理员权限,对之后新建的会话生效) SET @@GLOBAL.autocommit = 0; SET GLOBAL autocommit = 0;
注意事项
- 用户不能增加或删除系统变量—— 系统变量是 MySQL 内置的,只能查看和修改;
- 修改全局变量需要权限—— 需要
SYSTEM_VARIABLES_ADMIN或SUPER权限,普通用户可能无法修改; - 全局变量修改不影响当前会话—— 只对之后新建的会话生效;
- 全局变量修改不持久化—— 重启 MySQL 后会恢复为配置文件中的值,要永久修改需写入配置文件(如
my.cnf)。
1.2 用户自定义变量
用户自定义变量是用户在当前会话中定义的变量,不需要提前声明,赋值即创建,作用域为当前会话。
用户变量以 @ 前缀标识,如 @var_name。
创建与赋值
方式一:SET 赋值(用 =)
SET @var_name = expr [, @var_name2 = expr2] ...;
方式二:SET 赋值(用 :=,推荐)
SET @var_name := expr [, @var_name2 := expr2] ...;
方式三:在 SELECT 中赋值(必须用 :=)
SELECT @var_name := expr [, @var_name2 := expr2] ...;
方式四:查询结果赋值给变量(INTO)
SELECT 列名 INTO @var_name FROM 表名 WHERE ...;
:= 与 = 的区别(重要)
在 SQL 中没有 ==,= 身兼两职 —— 既可以表示赋值,也可以表示比较(等于),在某些场景会产生歧义。
| 场景 | = | := |
|---|---|---|
| SET 语句中 | 赋值 | 赋值 |
| SELECT 语句中 | 比较运算(等于判断) | 赋值 |
所以在 SELECT 中赋值必须用 :=,用 = 会被当成比较。推荐统一使用 := 进行赋值,避免歧义。
示例
-- 方式一 SET @var1 := (SELECT ename FROM test WHERE id = 1); SELECT @var1; -- 方式二 SELECT @var2 := (SELECT ename FROM test WHERE id = 1); SELECT @var2; -- 方式三 SELECT ename INTO @var3 FROM test WHERE id = 1; SELECT @var3;
注意:用户变量是标量,只能存储一行一列的值。如果子查询返回多行,赋值会报错。
查看与删除
- 删除:没有专门的删除语句,不用时置为
NULL即可;断开会话(重新连接)后所有用户变量自动清除。 - 查看所有用户变量:MySQL 没有直接的 SHOW 命令,但可以通过
performance_schema查询(MySQL 5.7+):
SELECT
VARIABLE_NAME AS '变量名',
VARIABLE_VALUE AS '变量值'
FROM performance_schema.user_variables_by_thread
WHERE THREAD_ID = (
SELECT THREAD_ID
FROM performance_schema.threads
WHERE PROCESSLIST_ID = CONNECTION_ID()
);用户变量的特点
- 以
@前缀标识; - 不需要声明,赋值即创建;
- 作用域为当前会话,会话结束自动销毁;
- 变量名不区分大小写(
@var和@VAR是同一个变量); - 类型是动态的,可以随时赋值不同类型的值;
- 只能存储一行一列的标量值。
1.3 局部变量
局部变量是在存储过程、存储函数、触发器的 BEGIN...END 块内部定义的变量,作用域为该 BEGIN...END 块。
DECLARE 变量名 变量类型 [DEFAULT 默认值] [, 变量名2 类型2 ...];
⚠️ 重要:
1.DECLARE必须放在BEGIN...END块的最开头,在所有其他语句(包括 SET、SELECT 等)之前声明,否则报错。2.MySQL规定一个DECLARE语句中定义多个变量要求变量类型相同
示例
DELIMITER //
CREATE PROCEDURE P2()
BEGIN
-- 必须在最开头声明
DECLARE name VARCHAR(10) DEFAULT 'bksw';
SELECT name;
SELECT * FROM test WHERE ename = name;
END //
DELIMITER ;
CALL P2();赋值
SET var_name = new_value; -- 方式一 SET var_name := new_value; -- 方式二(推荐) SELECT 列名 INTO var_name FROM 表名 WHERE ...; -- 方式三
示例
BEGIN
DECLARE name VARCHAR(10) DEFAULT 'bksw',
age SMALLINT DEFAULT 18,
gender CHAR(1) DEFAULT '男';
SET name = 'hdsw';
SET age := 20;
SET gender = '女';
SELECT name, age, gender;
END注意事项
- 变量名不区分大小写;
- 变量名不可与关键字同名(如需使用可用反引号转义);
- 作用域为当前
BEGIN...END块(过程、函数、触发器内); - 使用前必须声明,且声明必须在块的最开头;
- 局部变量没有 @ 前缀,直接写变量名。
1.4 三种变量对比
| 对比项 | 系统变量 | 用户自定义变量 | 局部变量 |
|---|---|---|---|
| 标识 | @@ 前缀 | @ 前缀 | 无前缀 |
| 声明 | 无需声明(内置) | 无需声明(赋值即创建) | 必须 DECLARE 声明 |
| 作用域 | 全局 / 当前会话 | 当前会话 | 当前 BEGIN...END 块 |
| 可用位置 | 任何地方 | 任何地方 | 仅存储过程 / 函数 / 触发器内 |
| 类型 | 固定(由 MySQL 定义) | 动态(赋值时确定) | 声明时固定 |
| 生命周期 | 全局永久 / 会话期 | 会话期 | 块执行期间 |
二、分支语句
分支语句用于在存储程序(存储过程、存储函数、触发器)中做条件判断。
2.1 IF 语句
IF 条件1 THEN
statement_list
ELSEIF 条件2 THEN
statement_list
ELSE
statement_list
END IF;⚠️ 重要:MySQL 中是
ELSEIF(一个单词)。这和 SQL Server 等数据库不同,注意区分。
ELSEIF 和 ELSE 都是可选的,可以只有 IF...THEN...END IF。
示例
DELIMITER //
CREATE PROCEDURE p_if(IN score INT)
BEGIN
IF score >= 90 THEN
SELECT '优秀' AS result;
ELSEIF score >= 60 THEN
SELECT '及格' AS result;
ELSE
SELECT '不及格' AS result;
END IF;
END //
DELIMITER ;2.2 CASE 语句
CASE 语句有两种形式:
形式一:对表达式的值进行判断(类似 switch)
CASE case_value
WHEN val1 THEN statement_list
WHEN val2 THEN statement_list
...
ELSE statement_list
END CASE;形式二:对条件表达式进行判断
CASE
WHEN expr1 THEN statement_list
WHEN expr2 THEN statement_list
...
ELSE statement_list
END CASE;注意事项
- 不会穿透:匹配到一个 WHEN 后自动跳出,不会像 C 语言 switch 那样继续执行后续 WHEN(不需要 break);
- 无匹配且无 ELSE 会报错:如果没有任何 WHEN 匹配,且没有 ELSE 子句,会报错(Error 1339: Case not found for CASE statement);
- 这是存储程序中的 CASE 语句,和 SELECT 查询中的
CASE WHEN表达式不同 ——SELECT 中的 CASE 无匹配返回 NULL,不报错。
示例
DELIMITER //
CREATE PROCEDURE p3()
BEGIN
SET @var1 := 20;
SET @var2 := 10;
CASE @var1 + @var2
WHEN 20 THEN SELECT 20;
WHEN 10 THEN SELECT 10;
WHEN 30 THEN SELECT 30;
ELSE SELECT 'ERROR';
END CASE;
END //
DELIMITER ;
CALL p3();三、循环语句
3.1 WHILE 循环
循环语句用于在存储程序(存储过程、存储函数、触发器)中做流程控制语句。
WHILE search_condition DO
statement_list
END WHILE;- 先判断后执行:条件为真时执行循环体,条件为假时退出;
- 如果一开始条件就为假,循环体一次都不执行。
示例
WHILE i < 10 DO
SET i := i + 1;
END WHILE;3.2 REPEAT 循环
REPEAT
statement_list
UNTIL search_condition
END REPEAT;- 先执行后判断:先执行一次循环体,再判断条件;
- 条件成立时退出(注意和 WHILE 相反:WHILE 是条件成立时继续,REPEAT 是条件成立时退出);
- 循环体至少执行一次。
示例
REPEAT
SET i := i + 1;
UNTIL i >= 10
END REPEAT;3.3 LOOP 循环
[begin_label:] LOOP
statement_list
END LOOP [end_label];- LOOP 本身没有退出条件,必须用
LEAVE手动退出,否则是死循环; - 通常配合标签和
LEAVE/ITERATE使用。
示例
my_loop: LOOP
SET i := i + 1;
IF i >= 10 THEN
LEAVE my_loop; -- 退出循环
END IF;
END LOOP my_loop;3.4 LEAVE 与 ITERATE
| 语句 | 类比 | 作用 |
|---|---|---|
LEAVE label | C 语言的 break | 终止整个循环,跳出循环体 |
ITERATE label | C 语言的 continue | 终止本次循环,直接开始下一次循环 |
LEAVE和ITERATE不仅可以用于 LOOP,也可以用于带标签的 WHILE 和 REPEAT 循环。
示例
my_loop: LOOP
SET i := i + 1;
IF i = 5 THEN
ITERATE my_loop; -- 跳过 i=5,直接进入下一次
END IF;
IF i >= 10 THEN
LEAVE my_loop; -- i>=10 时退出
END IF;
SELECT i;
END LOOP my_loop;3.5 三种循环对比
| 对比项 | WHILE | REPEAT | LOOP |
|---|---|---|---|
| 执行顺序 | 先判断后执行 | 先执行后判断 | 先执行后判断(需手动退出) |
| 最少执行次数 | 0 次 | 1 次 | 1 次 |
| 退出条件 | 条件为假时退出 | 条件为真时退出 | LEAVE 手动退出 |
| 是否需要标签 | 可选 | 可选 | 通常需要(配合 LEAVE) |
四、游标
4.1 什么是游标
游标(Cursor)是一种数据库对象,允许在存储过程和函数中对查询结果集进行逐条检索。
- 游标保存的是当前行的数据,每次 fetch 取一行;
- MySQL 的游标是只读的(READ ONLY),不能通过游标更新数据;
- 游标是向前只读的(forward-only),只能从头到尾顺序读取,不能回退;
- 游标是不敏感的(asensitive),数据在服务器端,fetch 时传输到客户端。
4.2 游标的使用步骤
游标使用分四步:声明 → 打开 → 读取 → 关闭。
步骤一:声明游标
DECLARE 游标名 CURSOR FOR 查询语句;
CURSOR 就是游标的英语
步骤二:打开游标
OPEN 游标名;
打开游标时会执行声明时的查询语句,建立结果集。
步骤三:读取数据(FETCH)
FETCH 游标名 INTO 变量1[, 变量2, ...];
- 将游标当前行的数据按 SELECT 字段顺序拷贝给变量;
- 读取后游标自动指向下一行;
- 变量数量和类型必须与查询结果的列匹配。
步骤四:关闭游标
CLOSE 游标名;
关闭后释放资源。如果未显式关闭,存储程序结束时会自动关闭。
4.3 声明顺序要求(重要)
在存储程序中,DECLARE 的顺序有严格要求:
1. 先声明变量(DECLARE 变量)
2. 再声明游标(DECLARE CURSOR)
3. 最后声明条件处理程序(DECLARE HANDLER)
顺序不能乱,否则会报错。
4.4 游标基础示例
DELIMITER //
DROP PROCEDURE IF EXISTS p4 //
CREATE PROCEDURE p4()
BEGIN
DECLARE var1 SMALLINT DEFAULT 0;
DECLARE var2 VARCHAR(10);
DECLARE times TINYINT DEFAULT 3;
DECLARE cursor_name CURSOR FOR SELECT * FROM test;
OPEN cursor_name;
WHILE times > 0 DO
FETCH cursor_name INTO var1, var2;
SELECT var1, var2;
SET times := times - 1;
END WHILE;
CLOSE cursor_name;
END //
DELIMITER ;
CALL p4();这个示例只读取前 3 行。如果循环次数超过实际行数,fetch 到末尾时会报错 —— 这就需要条件处理程序来处理。
五、条件处理程序
5.1 什么是条件处理程序
条件处理程序(Handler)用于捕获存储程序执行过程中出现的错误或警告,并执行预设的处理逻辑,类似 C++ 中的 try-catch。
5.2 语法
DECLARE handler_action HANDLER
FOR condition_value [, condition_value] ...
statement_list5.3 handler_action(处理动作)
| 动作 | 含义 |
|---|---|
CONTINUE | 执行完处理程序后,继续执行后续语句 |
EXIT | 执行完处理程序后,退出当前 BEGIN...END 块 |
5.4 condition_value(触发条件)
| 形式 | 说明 | 示例 |
|---|---|---|
mysql_error_code | MySQL 错误码(数字) | 1329 |
SQLSTATE [VALUE] sqlstate_value | SQLSTATE 状态码(5 位字符串) | '02000' |
SQLWARNING | 所有以 01 开头的 SQLSTATE | — |
NOT FOUND | 所有以 02 开头的 SQLSTATE(游标越界属于此类) | — |
SQLEXCEPTION | 所有未被 SQLWARNING 或 NOT FOUND 捕获的 SQLSTATE | — |
优先级:具体的错误码 / 状态码 > 分类条件(SQLWARNING/NOT FOUND/SQLEXCEPTION)。可以定义多个处理程序,按最具体的匹配。
5.5 游标越界处理完整示例
问题:无限循环 fetch 会报什么错?
-- 无限循环读取游标
WHILE TRUE DO
FETCH cursor_name INTO var1, var2;
SELECT var1, var2;
END WHILE;当游标读取到结果集末尾后,再次 fetch 会报错:
ERROR 1329 (02000): No data - zero rows fetched, selected, or processed
- 错误码:
1329 - SQLSTATE:
02000(以 02 开头,属于NOT FOUND)
解决方案:用条件处理程序捕获 NOT FOUND
DELIMITER //
DROP PROCEDURE IF EXISTS p4 //
CREATE PROCEDURE p4()
BEGIN
DECLARE var1 SMALLINT DEFAULT 0;
DECLARE var2 VARCHAR(10);
DECLARE done BOOL DEFAULT TRUE; -- 开关变量
DECLARE cursor_name CURSOR FOR SELECT * FROM test;
DECLARE CONTINUE HANDLER FOR NOT FOUND -- 条件处理程序
SET done := FALSE;
OPEN cursor_name;
WHILE done DO -- 用开关控制循环
FETCH cursor_name INTO var1, var2;
IF done THEN -- 防止最后一次fetch越界后还执行
SELECT var1, var2;
END IF;
END WHILE;
CLOSE cursor_name;
END //
DELIMITER ;
CALL p4();执行流程说明
- 正常 fetch 时,
done保持TRUE,循环继续; - fetch 到最后一行之后,再次 fetch 触发
NOT FOUND; - 条件处理程序执行
SET done := FALSE; - 因为是
CONTINUE,继续执行后续语句(此时done已是FALSE,IF 内的 SELECT 不执行); - 回到 WHILE 判断,
done为FALSE,循环退出; - 关闭游标,过程结束。
这是 MySQL 游标遍历的标准模板:声明游标 → 声明 CONTINUE HANDLER FOR NOT FOUND 设置开关 → WHILE 开关循环 → FETCH → 处理数据。
到此这篇关于MySQL变量、分支、循环、游标与条件处理程序全攻略的文章就介绍到这了,更多相关mysql变量、分支、循环、游标与条件处理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
