Mysql

关注公众号 jb51net

关闭
首页 > 数据库 > Mysql > mysql变量、分支、循环、游标与条件处理

MySQL变量、分支、循环、游标与条件处理程序全攻略

作者:bksczm

本文介绍了MySQL中三类变量(系统变量、用户自定义变量、局部变量)的定义、作用域、使用方式及区别,并讲解了分支语句(IF、CASE)、循环语句(WHILE、REPEAT、LOOP)及游标与条件处理程序的用法,重点阐述了变量声明顺序、赋值语法差异、循环控制逻辑及游标遍历的标准模式

一、变量

MySQL 中的变量分为三类:系统变量、用户自定义变量、局部变量。

1.1 系统变量

系统变量是 MySQL 服务器内置的变量,用于控制服务器的行为。分为两个级别:

级别关键字作用范围
全局变量GLOBAL对所有会话生效,作为新建会话的初始化模板
会话变量SESSION仅对当前连接会话生效,会话结束即销毁

全局变量与会话变量的关系

当客户端与服务端握手成功、建立会话时:

  1. 会话变量从全局变量拷贝一份初始值;
  2. 之后会话变量独立运行,修改会话变量不影响全局变量;
  3. 修改全局变量只对之后新建的会话生效,对当前已存在的会话不生效。

一句话总结:全局变量是模板,会话变量是当前会话实际使用的副本。

查看系统变量

方式一: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;

注意事项

  1. 用户不能增加或删除系统变量—— 系统变量是 MySQL 内置的,只能查看和修改;
  2. 修改全局变量需要权限—— 需要 SYSTEM_VARIABLES_ADMIN 或 SUPER 权限,普通用户可能无法修改;
  3. 全局变量修改不影响当前会话—— 只对之后新建的会话生效;
  4. 全局变量修改不持久化—— 重启 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;

注意:用户变量是标量,只能存储一行一列的值。如果子查询返回多行,赋值会报错。

查看与删除

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()
);

用户变量的特点

  1. 以 @ 前缀标识;
  2. 不需要声明,赋值即创建;
  3. 作用域为当前会话,会话结束自动销毁;
  4. 变量名不区分大小写(@var 和 @VAR 是同一个变量);
  5. 类型是动态的,可以随时赋值不同类型的值;
  6. 只能存储一行一列的标量值。

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

注意事项

  1. 变量名不区分大小写;
  2. 变量名不可与关键字同名(如需使用可用反引号转义);
  3. 作用域为当前 BEGIN...END 块(过程、函数、触发器内);
  4. 使用前必须声明,且声明必须在块的最开头;
  5. 局部变量没有 @ 前缀,直接写变量名。

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;

注意事项

  1. 不会穿透:匹配到一个 WHEN 后自动跳出,不会像 C 语言 switch 那样继续执行后续 WHEN(不需要 break);
  2. 无匹配且无 ELSE 会报错:如果没有任何 WHEN 匹配,且没有 ELSE 子句,会报错(Error 1339: Case not found for CASE statement);
  3. 这是存储程序中的 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;

示例

REPEAT
    SET i := i + 1;
UNTIL i >= 10
END REPEAT;

3.3 LOOP 循环

[begin_label:] LOOP
    statement_list
END LOOP [end_label];

示例

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 labelC 语言的 break终止整个循环,跳出循环体
ITERATE labelC 语言的 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 三种循环对比

对比项WHILEREPEATLOOP
执行顺序先判断后执行先执行后判断先执行后判断(需手动退出)
最少执行次数0 次1 次1 次
退出条件条件为假时退出条件为真时退出LEAVE 手动退出
是否需要标签可选可选通常需要(配合 LEAVE)

四、游标

4.1 什么是游标

游标(Cursor)是一种数据库对象,允许在存储过程和函数中对查询结果集进行逐条检索。

4.2 游标的使用步骤

游标使用分四步:声明 → 打开 → 读取 → 关闭。

步骤一:声明游标

DECLARE 游标名 CURSOR FOR 查询语句;

CURSOR 就是游标的英语

步骤二:打开游标

OPEN 游标名;

打开游标时会执行声明时的查询语句,建立结果集。

步骤三:读取数据(FETCH)

FETCH 游标名 INTO 变量1[, 变量2, ...];

步骤四:关闭游标

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_list

5.3 handler_action(处理动作)

动作含义
CONTINUE执行完处理程序后,继续执行后续语句
EXIT执行完处理程序后,退出当前 BEGIN...END 块

5.4 condition_value(触发条件)

形式说明示例
mysql_error_codeMySQL 错误码(数字)1329
SQLSTATE [VALUE] sqlstate_valueSQLSTATE 状态码(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

解决方案:用条件处理程序捕获 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();

执行流程说明

  1. 正常 fetch 时,done 保持 TRUE,循环继续;
  2. fetch 到最后一行之后,再次 fetch 触发 NOT FOUND;
  3. 条件处理程序执行 SET done := FALSE;
  4. 因为是 CONTINUE,继续执行后续语句(此时 done 已是 FALSE,IF 内的 SELECT 不执行);
  5. 回到 WHILE 判断,done 为 FALSE,循环退出;
  6. 关闭游标,过程结束。

这是 MySQL 游标遍历的标准模板:声明游标 → 声明 CONTINUE HANDLER FOR NOT FOUND 设置开关 → WHILE 开关循环 → FETCH → 处理数据。

到此这篇关于MySQL变量、分支、循环、游标与条件处理程序全攻略的文章就介绍到这了,更多相关mysql变量、分支、循环、游标与条件处理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文