变量可以分为局部变量 、用户变量 、会话变量 、全局变量。会话变量和全局变量叫系统变量,系统变量由系统定义并维护。
一、系统变量
1、系统变量的基本定义
-
是 MySQL 实例的系统参数,用于配置数据库的资源占用、文件位置等;
-
来源:编译默认值、
my.cnf配置文件; -
命名规则:默认小写,以两个
@符号开头(如@@max_connections)。
2、系统变量的分类(按作用域)
按作用域分为 2 类:
-
全局级别系统变量:
-
作用域:整个 MySQL 实例(所有会话生效);
-
标识:
Scope值为GLOBAL。
-
-
会话级别系统变量:
-
作用域:仅当前数据库连接(会话)生效;
-
标识:
Scope值为SESSION(部分变量同时支持GLOBAL和SESSION)。
-
3、系统变量的操作语法
3.1. 设置全局变量(2 种方式)
-- 方式1:用GLOBAL关键字
SET GLOBAL max_connections = 1000;
-- 方式2:用@@GLOBAL.前缀
SET @@GLOBAL.max_connections = 1000;
3.2. 设置会话变量(3 种方式)
-- 方式1:用SESSION关键字
SET SESSION sql_mode = 'TRADITIONAL';
-- 方式2:用@@SESSION.前缀
SET @@SESSION.sql_mode = 'TRADITIONAL';
-- 方式3:省略SESSION(默认会话级别)
SET @@sql_mode = 'TRADITIONAL';
3.3. 查看所有系统变量
SHOW VARIABLES;
二、用户变量
1、用户变量的基本定义
-
格式:以
@开头(如@age),无需提前声明,直接使用; -
作用域:当前数据库连接(会话),仅当前会话可见,会话结束后自动释放。
2、用户变量的赋值方式
使用SET语句赋值,支持 2 种赋值符号:
-- 方式1:= 赋值
set @age=19;
-- 方式2::= 赋值
set @age:=20;
3、用户变量的核心特性
-
跨语句传递值:可在一个语句中存储值,在另一个语句中引用,实现值的跨语句传递;
-
3.2命名规则:
-
由字母、数字、
.、_、$组成; -
不区分大小写,最大长度 64 个字符;
-
-
会话隔离性:一个客户端的用户变量,其他客户端无法访问(仅有权限查看
user_variables_by_thread表的用户例外)。
三、局部变量
3.1、局部变量的基本定义
-
使用范围:仅能在
BEGIN...END语句块中使用(如存储过程、函数、触发器的BEGIN...END内); -
作用域:仅限于所在的
BEGIN...END语句块,块外无法访问。
3.2、局部变量的定义方式
通过DECLARE语句定义,支持指定默认值:
-- 格式:DECLARE 变量名 数据类型 [DEFAULT 默认值]
declare age int default 0; -- 定义int类型局部变量age,默认值0
3.3、局部变量的赋值方式
-
方式 1:用
SET赋值
set age=18; -- 直接给局部变量age赋值为18
-
方式 2:用
SELECT ... INTO从表中查询赋值
-- 从student表中查询StuNo='A001'的StuAge,赋值给局部变量age
select StuAge into age from demo.student where StuNo='A001';
四、自定义函数
4.1语法逐段详解
| 语法片段 | 核心作用 | 补充说明 |
|---|---|---|
delimiter $$ | 临时修改 SQL 语句结束符 | MySQL 默认结束符是 ;,而函数体内部会用到 ;,若不修改结束符,MySQL 会误将函数体内的 ; 当作整个函数定义的结束信号,导致语法错误。$$ 是自定义结束符(也可改为 // 等其他符号,只需前后一致即可) |
create function function_name | 声明创建自定义函数 | function_name 是你给函数起的名称(需符合 MySQL 命名规范,不能与内置函数重名,如 get_score_level) |
([parameter1 类型1[,...]]) | 定义函数的输入参数 | 1. 参数格式:参数名 数据类型(如 stu_score INT、user_name VARCHAR(20))2. 可定义多个参数,用逗号分隔3. 自定义函数仅支持输入参数(无 IN/OUT/INOUT 关键字,默认都是输入参数,只能传入值,不能像存储过程那样返回输出参数) |
returns type | 指定函数的返回值类型 | 必须明确声明函数最终要返回的数据类型(如 INT、VARCHAR(10)、DATE 等),且函数体内部必须有 return 语句返回该类型的值,否则报错 |
[characteristic ..] | 函数特性配置(可选) | 可选配置项,常用的有:1. DETERMINISTIC:确定性函数(输入相同值,返回结果必然相同,如根据成绩判断等级)2. NOT DETERMINISTIC:非确定性函数(输入相同值,返回结果可能不同,如获取当前时间)3. SQL SECURITY DEFINER:以函数创建者的权限执行(默认)4. SQL SECURITY INVOKER:以函数调用者的权限执行 |
begin ... end | 包裹函数的实现逻辑 | 函数的核心业务逻辑(变量声明、条件判断、循环、查询等)都写在 begin 和 end 之间(若逻辑只有一行,可省略 begin 和 end) |
函数实现逻辑; | 自定义的业务逻辑 | 可包含变量声明(DECLARE)、赋值(SET/SELECT INTO)、流程控制(IF/CASE/WHILE)等数据库编程语法 |
return xx; | 返回函数结果 | 1. 必须存在,且 xx 的数据类型必须与 returns type 声明的类型一致2. 执行到 return 语句后,函数会立即终止,后续逻辑不再执行 |
end $$ | 标记函数定义结束 | 与开头 delimiter $$ 对应,告诉 MySQL 函数定义到此结束 |
delimiter ; | 恢复默认 SQL 结束符 | 将临时修改的 $$ 结束符恢复为 MySQL 默认的 ;,不影响后续普通 SQL 语句的执行 |
4.2、模板以及完整实例演示
4.2.1.自定义函数创建模板
delimiter $$ create function 自定义函数名(变量名1 类型1,)
returns type(返回值类型) [characteristic]
(1. DETERMINISTIC:确定性函数(输入相同值,返回结果必然相同,如根据成绩判断等级)
2. NOT DETERMINISTIC:非确定性函数(输入相同值,返回结果可能不同,如获取当前时间)
3. SQL SECURITY DEFINER:以函数创建者的权限执行(默认)
4. SQL SECURITY INVOKER:以函数调用者的权限执行)
begin
函数实现逻辑;
return 返回的变量名(与声明returns的type相同);
end $$
delimiter ;
4.2.2.自定义函数调用代码
调用函数的语法格式:
select function_name(parameter1 ,...);
4.2.3.
结合 “成绩等级判断” 的实际场景,套用该语法创建函数,更直观理解:
-- 1. 临时修改结束符
delimiter $$
-- 2. 创建自定义函数:根据成绩返回等级(优秀/良好/合格/不合格)
create function get_score_level(stu_score INT) -- 输入参数:成绩(整数类型)
returns VARCHAR(10) -- 返回值类型:字符串(最长10个字符)
DETERMINISTIC -- 特性:确定性函数(同一成绩返回同一等级)
begin
-- 函数实现逻辑:声明变量+条件判断+赋值
DECLARE score_level VARCHAR(10); -- 声明局部变量存储等级
IF stu_score >= 90 THEN
SET score_level = '优秀';
ELSEIF stu_score >= 80 THEN
SET score_level = '良好';
ELSEIF stu_score >= 60 THEN
SET score_level = '合格';
ELSE
SET score_level = '不合格';
END IF;
return score_level; -- 返回结果(与returns声明的VARCHAR(10)一致)
end $$ -- 函数定义结束
-- 3. 恢复默认结束符
delimiter ;
4.3、函数调用方式
创建成功后,可像 MySQL 内置函数一样直接调用:
-- 1. 单独调用
SELECT get_score_level(92) AS 成绩等级; -- 输出:优秀
-- 2. 结合表查询调用
SELECT sno, sname, score, get_score_level(score) AS 成绩等级
FROM student;
4.4、核心特性总结
- 返回值唯一性:自定义函数必须返回单个值(这是与存储过程的核心区别之一,存储过程可无返回值或返回多个输出参数);
- 复用性:一次创建,可在查询、存储过程、触发器等多个场景中重复调用;
- 语法约束:必须指定
returns类型,且函数体内必须有return语句,否则无法创建成功。
五、MySQL主要支持算术运算符、比较运算符、逻辑运算符和位运算符四种类型。
5.1算术运算符 算运算符对两个表达式执行数学运算,参与运算的表达式必须是数值数据类型或能够进行算术运算的其它数据类型。
|
运算符 |
名称 |
语法 |
|
+ |
加 |
Expression1 + Expression2 |
|
- |
减 |
Expression1 - Expression2 |
|
* |
乘 |
Expression1 * Expression2 |
|
/ |
除 |
Expression1 / Expression2 |
|
% |
取余 |
Expression1 % Expression2 |
5.2MySQL数据库允许用户对表达式的左边操作数和右边操作数进行比较,比较结果为真返回1,为假返回0,不确定返回NULL。
|
运算符 |
作用 |
简单实例 |
|---|---|---|
|
|
等于 |
|
|
|
不等于 |
|
|
|
NULL 安全的等于 |
|
|
|
小于 |
|
|
|
小于等于 |
|
|
|
大于 |
|
|
|
大于等于 |
|
|
|
在 min 和 max 之间(含边界) |
|
|
|
存在于指定集合中 |
|
|
|
为 NULL |
|
|
|
不为 NULL |
|
|
|
通配符匹配 |
|
|
|
正则表达式匹配 |
|
5.3逻辑运算符 逻辑运算符用来对多个条件进行运算,运算的结果为True或False,通常用来表示复杂的条件表达式。
| 运算符 | 作用 | 逻辑规则 | 实例(基于 student 表) |
|---|---|---|---|
NOT 或 ! | 逻辑非 | 取反(真→假,假→真) | SELECT * FROM student WHERE NOT (score >= 60);(查询成绩不及格的学生) |
AND 或 && | 逻辑与 | 两边都为真,结果才为真 | SELECT * FROM student WHERE score >= 80 AND class = '1班';(查询 1 班成绩≥80 的学生) |
OR 或 || | 逻辑或 | 任意一边为真,结果即为真 | SELECT * FROM student WHERE score >= 90 OR age = 18;(查询成绩≥90 或 年龄 18 的学生) |
XOR | 逻辑异或 | 两边一真一假,结果才为真 | SELECT * FROM student WHERE score >= 90 XOR class = '1班';(仅成绩≥90 或 仅 1 班的学生) |
5.4位运算是将给定的操作数转化为二进制后,对各个操作数每一位都进行指定的逻辑运算,得到的二进制结果转换为十进制数后就是位运算的结果
|
运算符 |
说明 |
|
& |
按位与 |
|
| |
按位或 |
|
^ |
按位异或 |
|
~ |
位取反 |
|
>> |
位右移 |
|
<< |
位左移 |
六、常用函数
函数可以用来构造表达式,可以出现在select语句的选择列表中,也可以出现在where子句的条件中。
6.1数学函数
abs ( numeric_expression ):返回指定数值表达式的绝对值
round ( numeric_expression , length [ ,function ] ):返回一个舍入到指定的长度或精度的数值
power ( float_expression, y ):返回指定表达式的指定幂的值
sqrt ( float_expression ):返回指定表达式的平方根
exp ( float_expression ):返回指定的表达式的指数值
log ( float_expression ):返回指定表达式的自然对数
sin ( float_expression ):返回指定角度(以弧度为单位)的三角正弦值
cos ( float_expression ):返回指定角度(以弧度为单位)的三角余弦值
6.2字符串函数
char_length(str):返回str所包含的字符数,一个多字节字符算一个字符
length(str): 返回字符串的字节长度,如utf8中,一个汉字3字节,数字和字母算一个字节
concat(str1, str2, ...): 返回连接参数产生的字符串
insert(str,pos,len,newstr):返回str,其起始于pos,长度为len的子串被
newstr取代 left(str,n):返回字符串s最左边n个字符
right(str,n): 返回字符串最右边n个字符 ltrim( str ):返回删除了前导空格之后的字符表达式
rtrim ( str ):截断所有尾随空格后返回一个字符串
repeat(str,count):返回str重复count次得到的新字符串
replace(str,from_str,to_str): 将str中的from_str全部替换成to_str
6.3日期和时间函数
curdate( ):返回系统当前的日期
curtime( ):返回系统当前的时间 year ( date ):返回表示指定参数 date 的“年”部分的整数
month ( date ):返回表示指定参数 date 的“月”部分的整数
day ( date ):返回表示指定参数date 的“日”部分的整数
adddate(expr,days):将一个时间间隔days与指定 expr 参数相加,返回一个新的 date值
datediff(expr1,expr2):返回相差的天数
timediff(expr1,expr2):返回相隔的时间
七、begin...end语句
BEGIN...END 在 MySQL 数据库编程中(存储过程、函数、触发器等),作用是 将多条 SQL 语句封装成一个 “代码块”(逻辑单元),相当于编程语言中的 {} 大括号。它能让多个 SQL 语句作为一个整体执行,支持嵌套使用,是实现复杂逻辑的基础
7.1begin...end模板
BEGIN
-- 多条SQL语句组成的逻辑块
sql语句1;
sql语句2;
...
END
7.2begin...end例子
DELIMITER $$
CREATE PROCEDURE check_score(IN stu_score INT)
BEGIN
-- BEGIN...END包裹多语句逻辑
IF stu_score >= 90 THEN
BEGIN -- 嵌套BEGIN...END(增强可读性)
SELECT '优秀' AS 成绩等级;
SELECT '可参与评优' AS 备注;
END;
ELSEIF stu_score >= 60 THEN
SELECT '及格' AS 成绩等级;
ELSE
SELECT '不及格' AS 成绩等级;
END IF;
END $$
DELIMITER ;
-- 调用
CALL check_score(95); -- 输出“优秀”+“可参与评优”
八、标签书写
8.1、各语句的标签模板
| 语句类型 | 语法模板 |
|---|---|
BEGIN...END | [标签名:] BEGIN 语句块 END [标签名] |
LOOP循环 | [标签名:] LOOP 语句块 END LOOP [标签名] |
REPEAT循环 | [标签名:] REPEAT 语句块 UNTIL 条件 END REPEAT [标签名] |
WHILE循环 | [标签名:] WHILE 条件 DO 语句块 END WHILE [标签名] |
8.2、标签使用规则
-
标签名后必须跟冒号(:); -
结束标签可选,但存在时必须与开始标签同名;
-
同一嵌套层级的标签不能重复;
-
标签最长 16 个字符。
8.3标签实例
DELIMITER $$
CREATE PROCEDURE loop_with_label()
BEGIN
DECLARE i INT DEFAULT 1;
-- 定义标签:num_loop
num_loop: LOOP
SELECT CONCAT('当前数字:', i) AS 输出;
-- 满足条件则离开标签对应的循环
IF i >= 3 THEN
LEAVE num_loop;
END IF;
SET i = i + 1;
END LOOP num_loop; -- 结束标签与开始标签同名
END $$
DELIMITER ;
-- 调用
CALL loop_with_label(); -- 输出“当前数字:1”“当前数字:2”“当前数字:3”
九、IF...ELSE 语句
9.1、IF...ELSE 语法模板
IF 条件1 THEN
-- 条件1满足时执行的语句块
语句1;
语句2;
[ELSEIF 条件2 THEN
-- 条件2满足时执行的语句块
语句3;
语句4;]
[ELSE
-- 所有条件不满足时执行的语句块
语句5;]
END IF;
9.2IF...ELSE 例子
函数功能
输入两个整数 n 和 m,比较大小后返回形如 “n 符号 m” 的字符串(如 “5 > 3”)。
完整代码(含注释)
-- 1. 修改语句结束符(避免函数体内的;被误判)
DELIMITER //
-- 2. 创建函数
CREATE FUNCTION SimpleCompare(n INT, m INT)
RETURNS VARCHAR(20) -- 返回字符串类型结果
BEGIN
DECLARE s VARCHAR(20); -- 声明局部变量存比较符号
-- IF...ELSE条件判断
IF n > m THEN
SET s = '>'; -- 条件1:n>m,符号为>
ELSEIF n = m THEN
SET s = '='; -- 条件2:n=m,符号为=
ELSE
SET s = '<'; -- 所有条件不满足:n<m,符号为<
END IF; -- 结束条件判断
-- 拼接结果字符串(如“5 > 3”)
SET s = CONCAT(n, ' ', s, ' ', m);
RETURN s; -- 返回结果
END //
-- 3. 恢复默认结束符
DELIMITER ;
十、CASE语句
10.1.1类型 1:简单 CASE(匹配固定值)模板
CASE case_value -- 测试值(固定值)
WHEN when_value THEN 语句块1; -- 匹配when_value时执行
[WHEN when_value THEN 语句块2;] ... -- 多个匹配分支
[ELSE 语句块n;] -- 无匹配时执行(可选)
END CASE;
10.1.2类型一例子
-- 解决错误1418的前置配置(可选)
SET GLOBAL log_bin_trust_function_creators = 1;
-- 删除旧函数(若存在)
DROP FUNCTION IF EXISTS test_function;
-- 创建函数
DELIMITER $$
CREATE FUNCTION test_function(number INT) RETURNS VARCHAR(10)
BEGIN
-- 简单CASE:匹配number的固定值
CASE number
WHEN 1 THEN SET @sex = '男';
WHEN 2 THEN SET @sex = '女';
ELSE SET @sex = '未知';
END CASE;
RETURN @sex;
END $$
DELIMITER ;
-- 调用
SELECT test_function(1); -- 输出:男
SELECT test_function(3); -- 输出:未知
10.2.1类型 2:搜索 CASE(匹配条件表达式)模板
CASE -- 无测试值,直接匹配条件
WHEN 条件表达式1 THEN 语句块1;
[WHEN 条件表达式2 THEN 语句块2;] ...
[ELSE 语句块n;]
END CASE;
10.2.2类型二例子
SET GLOBAL log_bin_trust_function_creators = 1;
DROP FUNCTION IF EXISTS test_function;
DELIMITER $$
CREATE FUNCTION test_function(score INT) RETURNS VARCHAR(10)
BEGIN
-- 搜索CASE:匹配score的范围条件
CASE
WHEN score > 100 THEN SET @level = '成绩错误';
WHEN score >= 90 THEN SET @level = '优秀';
WHEN score >= 80 THEN SET @level = '良好';
WHEN score >= 70 THEN SET @level = '中等';
WHEN score >= 60 THEN SET @level = '及格';
ELSE SET @level = '不及格';
END CASE;
RETURN @level;
END $$
DELIMITER ;
-- 调用
SELECT test_function(95); -- 输出:优秀
SELECT test_function(55); -- 输出:不及格
10.3.1普通查询中的 CASE 运算符模板
CASE
WHEN 条件 THEN 结果1
[WHEN 条件 THEN 结果2] ...
[ELSE 结果n]
END AS 别名
10.3.2例子
先创建课程表并插入数据:
CREATE TABLE courses (
name VARCHAR(10),
course VARCHAR(20)
);
INSERT INTO courses VALUES
('古田', 'UNIX基础'),
('工藤', 'Java中级'),
('工藤', 'SQL入门'),
('渡边', 'SQL入门'),
('赤井', 'SQL入门'),
('赤井', 'UNIX基础'),
('铃木', 'SQL入门');
-
用 CASE 运算符实现行列转换:
SELECT DISTINCT a.name,
-- 判断是否选修UNIX基础
CASE WHEN a.name IN (SELECT c.name FROM courses c WHERE c.course='UNIX基础') THEN '√' ELSE '○' END AS unix,
-- 判断是否选修Java中级
CASE WHEN a.name IN (SELECT c.name FROM courses c WHERE c.course='Java中级') THEN '√' ELSE '○' END AS java,
-- 判断是否选修SQL入门
CASE WHEN a.name IN (SELECT c.name FROM courses c WHERE c.course='SQL入门') THEN '√' ELSE '○' END AS sql
FROM courses a;
10.4对比
| 特性 | CASE 语句(存储程序中) | CASE 运算符(普通查询中) |
|---|---|---|
| 终止符 | END CASE | END |
| 用途 | 存储程序的流程控制 | 查询中字段的条件转换 |
| 执行结果 | 执行语句块 | 返回一个值(用于字段) |
十一、LOOP语句
11.1LOOP模板
[标签名:] LOOP
-- 循环内逻辑
IF 退出条件 THEN
LEAVE 标签名; -- 满足条件,终止循环
END IF;
IF 跳过条件 THEN
ITERATE 标签名; -- 满足条件,跳过当前循环剩余逻辑,进入下一次
END IF;
-- 业务处理逻辑(仅当不跳过、不退出时执行)
END LOOP [标签名];
11.2LOOP例子
计算 1~upper_limit 中奇数的和(跳过偶数),达到 upper_limit 时终止循环。
-- 解决错误1418的前置配置
SET GLOBAL log_bin_trust_function_creators = 1;
-- 删除旧函数(若存在)
DROP FUNCTION IF EXISTS test_loop;
-- 创建函数
DELIMITER $$
CREATE FUNCTION test_loop(upper_limit INT) RETURNS INT
BEGIN
DECLARE sum INT DEFAULT 0; -- 存储奇数和
DECLARE loop_variable INT DEFAULT 0; -- 循环计数器
-- 定义循环标签:loop_begin
loop_begin: LOOP
SET loop_variable = loop_variable + 1; -- 计数器自增
-- 条件1:计数器≥上限,退出循环
IF loop_variable >= upper_limit THEN
LEAVE loop_begin;
END IF;
-- 条件2:计数器是偶数,跳过当前循环(不执行求和)
IF loop_variable % 2 = 0 THEN
ITERATE loop_begin;
END IF;
-- 业务处理:计数器是奇数,累加求和
SET sum = sum + loop_variable;
END LOOP loop_begin; -- 结束循环
RETURN sum; -- 返回奇数和
END $$
DELIMITER ;
-- 调用(计算1~100的奇数和)
SELECT test_loop(100); -- 输出:2500(1+3+5+...+99=2500)
十二、LEAVE语句
leave语句主要用于跳出循环。语法形式如下:
leave label
其中label参数表示循环标志
十三、iterate语句
iterate语句主要用于跳出本次循环,然后进入下一轮循环。
基本语法形式:
iterate label
其中label参数表示循环标志
十四、REPEAT语句(标签可选)
14.1、REPEAT 是先执行、后判断的循环语句(至少执行 1 次),满足条件时跳出循环,模板如下:
[标签名:] REPEAT
-- 循环内执行的语句块(至少执行1次)
statement_list;
UNTIL 退出条件 -- 满足该条件时,跳出循环
END REPEAT [标签名];
14.2、REPEAT例子
实例 1: dorepeat 存储过程(自增到指定值)
功能
将用户变量 @x 从 0 开始自增,直到 @x > p1(参数值)时终止循环。
DELIMITER $$
CREATE PROCEDURE dorepeat(p1 INT)
BEGIN
SET @x = 0; -- 初始化用户变量@x为0
-- REPEAT循环:先执行语句,再判断条件
REPEAT
SET @x = @x + 1; -- @x自增1(至少执行1次)
UNTIL @x > p1 -- 条件:@x > p1时,跳出循环
END REPEAT;
END $$
DELIMITER ;
-- 调用并查看结果
CALL dorepeat(5); -- 参数p1=5,@x自增到6时满足“6>5”
SELECT @x; -- 输出:6
十五、WHILE语句
15.1WHILE模板
WHILE 是先判断、后执行的循环语句(条件不满足时可能一次都不执行),模板如下:
[标签名:] WHILE 循环条件 DO
-- 循环条件满足时,执行的语句块
statement_list;
END WHILE [标签名];
15.2WHILE例子
功能:将局部变量 v1 从 5 开始递减,直到 v1 ≤ 0 时终止循环。
DELIMITER $$
CREATE PROCEDURE dowhile()
BEGIN
DECLARE v1 INT DEFAULT 5; -- 初始化局部变量v1为5
-- WHILE循环:先判断条件“v1>0”,满足则执行循环体
WHILE v1 > 0 DO
SET v1 = v1 - 1; -- v1递减1
END WHILE;
END $$
DELIMITER ;
-- 调用(执行后v1最终为0)
CALL dowhile();
15.3对比
|
对比维度 |
|
|
|
|---|---|---|---|
|
执行逻辑 |
无限循环(无内置终止条件) |
先判断条件,后执行循环体 |
先执行循环体,后判断条件 |
|
循环体执行次数 |
至少执行 0 次(需手动加 |
可能执行 0 次(条件不满足时) |
至少执行 1 次(先执行再判断) |
|
终止方式 |
必须通过 |
条件为假时自动终止 |
条件为真时自动终止 |
十六、游标
16.1游标的理解
游标是 MySQL 中专门用于逐行遍历查询结果集的工具,核心价值是打破 SQL “批量处理数据” 的默认特性,实现 “一行一行读取、处理数据” 的需求,下面从核心本质、关键特性、使用逻辑三个维度帮你彻底理解:
16.1.1、游标核心本质:结果集的 “遍历指针”
可以把游标直观理解为:
-
先通过 SQL 查询获取一个完整的结果集(比如所有学生的成绩数据),游标会将这个结果集暂存起来;
-
游标本身就像一个可移动的指针,初始时指向结果集的第一条记录之前;
-
通过
FETCH语句,指针会向后移动,每次只读取当前行的数据到指定变量中,供后续逻辑处理; -
循环执行
FETCH,指针就会逐行遍历整个结果集,直到所有行都被读取完毕。
简单说:游标 = 结果集 + 逐行读取指针,它不存储数据,只提供 “逐个提取结果集行数据” 的能力。
16.1.2、游标核心特性(MySQL 专属,关键限制)
-
仅支持 “只读向前”(非滚动游标)这是 MySQL 游标的核心限制,也是和其他数据库游标最大的区别:
-
只读:不能通过游标修改对应的原始数据表数据,只能读取结果集行数据;
-
向前:指针只能从结果集第一行往后遍历,无法回滚(不能回到上一行、首行,也不能跳转到指定行),遍历过的行无法再次读取(除非重新打开游标)。
-
-
仅适用于存储程序游标不能在普通 SQL 查询中直接使用,只能在存储过程、自定义函数、触发器的
BEGIN...END语句块内声明和使用。 -
必须配合 “结束处理器” 使用游标没有内置的 “遍历完毕” 提示,必须通过
DECLARE CONTINUE HANDLER FOR NOT FOUND声明一个异常处理器,当游标读取不到下一行数据时(遍历完毕),自动设置标记变量(如done=1),用于终止循环,避免报错。 -
资源需手动释放游标打开后会占用数据库资源,遍历完毕后必须通过
CLOSE语句手动关闭游标,释放资源(类似文件操作的 “打开 - 关闭” 流程)。
16.1.3、游标核心使用逻辑
游标的使用有严格的流程规范,核心四步如下,是理解和使用游标的关键:
步骤 1:声明游标(绑定结果集)
先定义游标,并关联一个具体的 SQL 查询语句,确定要遍历的结果集。语法:DECLARE 游标名 CURSOR FOR SELECT 语句;作用:相当于 “提前约定要遍历的数据范围”,此时还未执行查询,也未加载数据。
步骤 2:打开游标(加载结果集)
通过OPEN语句执行游标关联的 SQL 查询,将查询结果集加载到游标中,此时指针指向结果集第一条记录之前。语法:OPEN 游标名;作用:相当于 “初始化遍历指针,准备开始读取数据”。
步骤 3:读取游标(逐行提取数据)
通过FETCH语句将游标指针指向的当前行数据,读取到预先声明的局部变量中,然后指针自动向后移动一行。语法:FETCH 游标名 INTO 变量1, 变量2, ...;(变量数量与 SELECT 结果列数一致)作用:这是 “逐行处理” 的核心步骤,每次执行仅获取一行数据,需配合循环(LOOP/WHILE)实现全量遍历。
步骤 4:关闭游标(释放资源)
遍历完毕后,通过CLOSE语句关闭游标,释放占用的数据库资源。语法:CLOSE 游标名;作用:避免资源泄露,是游标使用的收尾步骤。
16.1.4、游标核心适用场景(何时必须用游标?)
游标是 “低效但灵活” 的工具,优先不用,仅在必要时使用,核心适用场景是:当业务逻辑无法通过 “批量 SQL”(聚合函数、CASE、JOIN 等)实现时,必须依赖逐行处理的场景,典型示例:
-
逐行读取数据后,执行不同的复杂业务逻辑(比如根据每行数据的不同状态,执行不同的 INSERT/UPDATE/DELETE 操作,或触发不同的事务);
-
逐行处理多表关联的复杂更新(比如根据 A 表的每行数据,更新 B 表和 C 表的多条关联数据,批量 SQL 难以实现);
-
逐行生成复杂格式的输出(比如逐行拼接自定义字符串、生成个性化报表内容);
-
需逐行判断并累积复杂状态(比如逐行校验数据合法性,不合法则记录日志并跳过,合法则执行后续操作)。
16.1.5、游标(Cursor)声明规则
-
基本语法:通过
DECLARE 游标名 CURSOR FOR SELECT语句声明游标,关联查询结果集;后续用FETCH读取行数据,要求SELECT的列数与FETCH的变量数一致。 -
限制:
-
SELECT语句不能包含INTO子句; -
游标声明的顺序必须是:先声明变量 / 条件 → 再声明游标 → 最后声明处理器。
-
16.2游标模板
DELIMITER $$
CREATE PROCEDURE 存储过程名()
BEGIN
-- 1. 声明局部变量(对应游标结果集的列)
DECLARE 变量1 数据类型;
DECLARE 变量2 数据类型;
DECLARE done INT DEFAULT 0; -- 游标结束标记(类似链表“无下一个节点”的标识)
-- 2. 声明游标(PPT:定义指针+关联结果集)
DECLARE 游标名 CURSOR FOR
SELECT 列1, 列2 FROM 表名 [WHERE 条件]; -- 对应PPT的“结果集合”
-- 3. 声明游标结束处理器(捕获“无数据”异常)
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
-- 4. 打开游标(PPT:指针指向链表头节点)
OPEN 游标名;
-- 5. 第一次FETCH(PPT:访问当前节点)
FETCH 游标名 INTO 变量1, 变量2;
-- 6. WHILE循环(PPT:循环遍历)
WHILE done = 0 DO
-- 7. 业务处理(PPT:访问当前节点的业务逻辑)
业务操作语句; -- 用变量1、变量2实现业务逻辑
-- 8. 再次FETCH(PPT:指针后移)
FETCH 游标名 INTO 变量1, 变量2;
END WHILE;
-- 9. 关闭游标(释放资源)
CLOSE 游标名;
END $$
DELIMITER ;
16.3游标例子
以 “逐行读取学生信息并生成学号 + 姓名的拼接字符串” 为例,对应 PPT 的 “链表访问” 流程:
步骤 1:创建测试表(模拟 “链表数据”)
CREATE TABLE IF NOT EXISTS student (
sno CHAR(6), -- 学号
sname VARCHAR(20) -- 姓名
);
INSERT INTO student VALUES
('110101', '张三'),
('110102', '李四'),
('110103', '王五');
步骤 2:创建存储过程(游标 + 链表式遍历)
DELIMITER $$
CREATE PROCEDURE get_student_info()
BEGIN
-- 1. 声明局部变量(对应游标列)
DECLARE v_sno CHAR(6);
DECLARE v_sname VARCHAR(20);
DECLARE done INT DEFAULT 0;
-- 2. 声明游标(定义指针+关联student表)
DECLARE stu_cursor CURSOR FOR
SELECT sno, sname FROM student;
-- 3. 声明结束处理器
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
-- 4. 打开游标(指针指向链表头节点)
OPEN stu_cursor;
-- 5. 第一次FETCH(访问头节点)
FETCH stu_cursor INTO v_sno, v_sname;
-- 6. WHILE循环(循环遍历链表)
WHILE done = 0 DO
-- 7. 业务处理(访问当前节点:拼接字符串)
SELECT CONCAT('学号:', v_sno, ',姓名:', v_sname) AS 学生信息;
-- 8. 再次FETCH(指针后移到下一个节点)
FETCH stu_cursor INTO v_sno, v_sname;
END WHILE;
-- 9. 关闭游标
CLOSE stu_cursor;
END $$
DELIMITER ;
步骤 3:调用存储过程(验证链表式遍历)
CALL get_student_info();
执行结果:
学生信息
学号:110101,姓名:张三
学生信息
学号:110102,姓名:李四
学生信息
学号:110103,姓名:王五
十七、存储过程
17.1.1存储过程的核心优势是:
- 在服务器端处理数据:利用数据库服务器的计算能力,避免客户端 “下载大量数据后本地计算”;
- 减少网络传输量:服务器只返回最终计算结果,而非原始数据,提升效率。
17.1.2、对比 “不用存储过程” 和 “用存储过程” 的差异
假设我们有一个student_score表(10000 条学生成绩数据),需求是统计所有学生的平均分:
场景1:不用存储过程(客户端处理)
- 操作流程:
- 客户端执行
SELECT score FROM student_score;,将 10000 条成绩数据全部下载到客户端; - 客户端本地计算平均分(求和 ÷ 数量);
- 客户端执行
- 问题:
- 网络传输量极大(10000 条数据),耗时长;
- 客户端需要承担计算压力,效率低。
场景 2:用存储过程(服务器端处理)
创建存储过程(服务器端定义逻辑):
DELIMITER $$
CREATE PROCEDURE get_avg_score()
BEGIN
-- 服务器端直接计算平均分(利用服务器算力)
SELECT AVG(score) AS 学生平均分 FROM student_score;
END $$
DELIMITER ;
-
调用存储过程:
CALL get_avg_score();
-
-
客户端只发送
CALL指令(网络传输量极小); -
服务器端执行存储过程,在服务器内部完成 “读取 10000 条数据→计算平均分” 的操作;
-
服务器仅返回1 个结果(平均分)给客户端;
-
17.2存储过程的特点
17.2.1、存储过程核心特性
存储过程是数据库中的 “封装程序”,核心特点:
-
打包好的 SQL 语句集:把多步操作(如新增、日志、统计)封装成一个 “程序”;
-
可编程的函数:支持参数、流程控制(IF / 循环),像写代码一样实现数据库逻辑;
-
模拟面向对象的方法:封装逻辑、控制数据访问,调用时仅需指定名称 + 传参。
-
17.2.2、存储过程 vs 自定义函数 对比表
对比点
存储过程实例体现
函数实例体现
功能复杂度
实现 “新增 + 日志 + 统计” 多步复杂逻辑
仅实现 “成绩转等级” 单一功能
返回值 / 参数
用 3 个出参返回 “姓名 + 成绩 + 班级” 多结果
仅返回 1 个 “平均分” 值
声明要求
无需声明返回类型
必须声明返回类型 + 包含
RETURN语句17.2.3、自定义函数的多表操作说明(不建议insert、update等操作)
-
能否多表操作:MySQL 自定义函数可以进行多表操作(支持多表
JOIN查询、多表关联更新等); -
关键约束:函数多表操作后,必须返回单个值,无法返回多结果集,且不建议包含复杂事务;
-
使用场景:适合 “多表关联后汇总计算 / 拼接结果,最终返回单个值” 的场景(如统计指定班级的平均分、获取学生多表关联的个性化信息);
-
与存储过程的区别:存储过程无返回值数量限制,可直接返回多表查询的结果集,更适合复杂多表的增删改查 + 事务操作。
17.3.1存储过程模板
1. 基础模板
-- (可选)调试时删除旧存储过程
DROP PROCEDURE IF EXISTS 存储过程名;
-- 修改语句结束符
DELIMITER $$
-- 创建存储过程
CREATE PROCEDURE 存储过程名(
[参数类型1] 参数名1 数据类型,
[参数类型2] 参数名2 数据类型,
...
)
[特性...] -- (可选,如DETERMINISTIC)
BEGIN
-- 存储过程逻辑(SQL语句集)
业务SQL语句;
END $$
-- 恢复默认结束符
DELIMITER ;
-- 调用存储过程
CALL 存储过程名(参数值1, 参数值2, ...);
2.参数类型说明
| 参数类型 | 作用 | 示例 |
|---|---|---|
IN | 向存储过程传参(入参) | IN xbbh VARCHAR(4) |
OUT | 存储过程向外返回值(出参) | OUT total INT |
INOUT | 既传参又返回值 | INOUT num INT |
17.3.2存储过程例子
1.场景:根据系部编号查询学生信息 + 统计性别人数
-- 1. 创建“查询计算机系学生”的存储过程(IN参数)
DROP PROCEDURE IF EXISTS proc_jsjstu;
DELIMITER $$
CREATE PROCEDURE proc_jsjstu(IN xbbh VARCHAR(4))
BEGIN
SELECT * FROM stu WHERE SUBSTRING(id,5,2) = xbbh;
END $$
DELIMITER ;
-- 调用(查询计算机系,编号02)
CALL proc_jsjstu('02');
-- 2. 创建“统计系部性别人数”的存储过程(IN参数)
DROP PROCEDURE IF EXISTS proc_countsex;
DELIMITER $$
CREATE PROCEDURE proc_countsex(IN xbbh VARCHAR(4))
BEGIN
SELECT sex, COUNT(id) AS '人数合计'
FROM student
WHERE SUBSTRING(id,5,2) = xbbh
GROUP BY sex;
END $$
DELIMITER ;
-- 调用(统计计算机系性别人数)
CALL proc_countsex('02');
2.场景:传入数字,返回其平方值(既传参又返回)
-- 创建存储过程(INOUT参数)
DROP PROCEDURE IF EXISTS proc_square;
DELIMITER $$
CREATE PROCEDURE proc_square(INOUT num INT)
BEGIN
SET num = num * num; -- 修改入参,最终返回
END $$
DELIMITER ;
-- 调用(传入5,返回25)
SET @n = 5;
CALL proc_square(@n);
SELECT @n AS '数字的平方';
17.4.1存储过程带游标模板
-- 1. 调试时删除旧存储过程
DROP PROCEDURE IF EXISTS 存储过程名;
-- 2. 修改语句结束符
DELIMITER $$
-- 3. 创建存储过程(可选参数)
CREATE PROCEDURE 存储过程名(可选参数列表)
BEGIN
-- 3.1 声明局部变量(含游标结束标记)
DECLARE 变量1 数据类型; -- 对应游标结果列
DECLARE 变量2 数据类型;
DECLARE done INT DEFAULT TRUE; -- 游标结束标记(TRUE=未结束)
-- 3.2 声明游标(绑定结果集,多表关联查询)
DECLARE 游标名 CURSOR FOR
SELECT 列1, 列2 FROM 表1 JOIN 表2 ON 关联条件; -- 多表结果集
-- 3.3 声明游标结束处理器(必须,捕获无数据异常)
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = FALSE;
-- 3.4 (可选)其他变量初始化(如统计总和)
DECLARE 统计变量 数据类型 DEFAULT 初始值;
-- 3.5 打开游标(加载结果集)
OPEN 游标名;
-- 3.6 首次FETCH(读取第一行数据)
FETCH 游标名 INTO 变量1, 变量2;
-- 3.7 WHILE循环(标记循环,支持提前退出)
loopbegin: WHILE done DO
-- 3.8 业务处理(逐行计算/更新)
计算逻辑; -- 如:工资=岗位工资+薪级工资+绩效
更新/插入SQL; -- 如:将计算结果写入工资表
统计逻辑; -- 如:累加工资总额
-- 3.9 (可选)提前结束循环的条件
IF 条件 THEN
LEAVE loopbegin;
END IF;
-- 3.10 再次FETCH(读取下一行数据)
FETCH 游标名 INTO 变量1, 变量2;
END WHILE loopbegin;
-- 3.11 关闭游标(释放资源)
CLOSE 游标名;
-- 3.12 (可选)输出统计结果
SELECT 统计变量 AS 统计项;
END $$
-- 4. 恢复默认结束符
DELIMITER ;
-- 5. 调用存储过程
CALL 存储过程名(参数值);
17.4.2例子
场景:每月计算教职工工资(工资 = 岗位工资 + 薪级工资 + 绩效),并统计总额
步骤 1:创建基础表
-- 1. 教职工信息表
CREATE TABLE IF NOT EXISTS staff (
staff_id CHAR(6) PRIMARY KEY, -- 工号
post VARCHAR(20), -- 岗位
salary_level INT -- 薪级
);
-- 2. 岗位工资表
CREATE TABLE IF NOT EXISTS post_salary (
post VARCHAR(20) PRIMARY KEY,
post_salary DECIMAL(10,2) -- 岗位工资
);
-- 3. 薪级工资表
CREATE TABLE IF NOT EXISTS level_salary (
salary_level INT PRIMARY KEY,
level_salary DECIMAL(10,2) -- 薪级工资
);
-- 4. 绩效表
CREATE TABLE IF NOT EXISTS performance (
staff_id CHAR(6) PRIMARY KEY,
perf_salary DECIMAL(10,2) -- 绩效工资
);
-- 5. 工资表(存储计算结果)
CREATE TABLE IF NOT EXISTS salary (
staff_id CHAR(6) PRIMARY KEY,
total_salary DECIMAL(10,2), -- 总工资
calc_time DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- 插入测试数据
INSERT INTO staff VALUES ('S001', '讲师', 5), ('S002', '副教授', 8);
INSERT INTO post_salary VALUES ('讲师', 5000), ('副教授', 8000);
INSERT INTO level_salary VALUES (5, 2000), (8, 3500);
INSERT INTO performance VALUES ('S001', 1500), ('S002', 2000);
步骤 2:创建带游标的存储过程(计算工资)
DROP PROCEDURE IF EXISTS calc_staff_salary;
DELIMITER $$
CREATE PROCEDURE calc_staff_salary()
BEGIN
-- 声明局部变量(对应游标结果)
DECLARE v_staff_id CHAR(6);
DECLARE v_post VARCHAR(20);
DECLARE v_level INT;
DECLARE v_post_sal DECIMAL(10,2);
DECLARE v_level_sal DECIMAL(10,2);
DECLARE v_perf_sal DECIMAL(10,2);
DECLARE v_total DECIMAL(10,2);
DECLARE done INT DEFAULT TRUE; -- 游标结束标记
-- 声明统计变量(工资总额)
DECLARE total_sum DECIMAL(12,2) DEFAULT 0.0;
-- 声明游标(多表关联:用WHERE子句替代JOIN,实现等值连接)
DECLARE salary_cursor CURSOR FOR
SELECT s.staff_id, s.post, s.salary_level, ps.post_salary, ls.level_salary, p.perf_salary
FROM staff s, post_salary ps, level_salary ls, performance p -- 逗号分隔所有需要关联的表
-- WHERE子句中添加等值关联条件,对应原JOIN的关联逻辑
WHERE s.post = ps.post -- 对应 s JOIN ps ON s.post = ps.post
AND s.salary_level = ls.salary_level -- 对应 s JOIN ls ON s.salary_level = ls.salary_level
AND s.staff_id = p.staff_id; -- 对应 s JOIN p ON s.staff_id = p.staff_id;
-- 游标结束处理器
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = FALSE;
-- 打开游标
OPEN salary_cursor;
-- 首次FETCH
FETCH salary_cursor INTO v_staff_id, v_post, v_level, v_post_sal, v_level_sal, v_perf_sal;
-- WHILE标记循环
loopbegin: WHILE done DO
-- 业务处理:计算总工资
SET v_total = v_post_sal + v_level_sal + v_perf_sal;
-- 将结果写入工资表(批量更新)
REPLACE INTO salary (staff_id, total_salary) VALUES (v_staff_id, v_total);
-- 统计工资总额
SET total_sum = total_sum + v_total;
-- 再次FETCH
FETCH salary_cursor INTO v_staff_id, v_post, v_level, v_post_sal, v_level_sal, v_perf_sal;
END WHILE loopbegin;
-- 关闭游标
CLOSE salary_cursor;
-- 输出工资总额
SELECT total_sum AS '本月工资总额';
END $$
DELIMITER ;
步骤 3:调用存储过程并查看结果
-- 调用存储过程
CALL calc_staff_salary();
-- 查看工资计算结果
SELECT * FROM salary;
十八、触发器
18.1、触发器的核心定义
触发器是一种特殊的存储过程,核心特点:
-
自动执行:由 MySQL 自动触发,无法被应用程序主动调用(区别于普通存储过程);
-
触发条件:在对表执行
INSERT/UPDATE/DELETE操作时激活; -
核心用途:强制业务规则、保证数据完整性。
18.2、使用触发器的优势
-
自动性:对表的数据修改(手动 / 程序操作)后立即激活,无需人工干预;
-
安全的层叠修改:可通过相关表实现数据的关联修改(比前台代码控制更安全);
-
更复杂的强制限制:支持引用其他表的字段,能实现比
CHECK约束更复杂的规则。
18.3、触发器的作用
-
安全性:控制数据操作的权限(如限制特定时间修改数据);
-
审计:记录数据的修改历史(如记录谁、何时修改了数据);
-
复杂数据完整性:实现多表关联的完整性规则(如订单删除后自动删除关联的订单明细);
-
非标准完整性规则:满足业务特有的数据约束(如限制学生成绩只能在 0-100 之间)。
18.4、触发器创建完整模板
-- (可选)删除已存在的同名触发器(避免重复创建)
DROP TRIGGER IF EXISTS [数据库名.]触发器名;
-- 创建触发器
CREATE TRIGGER [数据库名.]触发器名
触发器时机 -- BEFORE/AFTER
触发器事件 -- INSERT/UPDATE/DELETE
ON 表名
FOR EACH ROW -- 行级触发器(MySQL仅支持此类型)
BEGIN
-- 触发器执行的业务逻辑(SQL语句集)
-- 可通过OLD/NEW引用数据:
-- INSERT事件:仅用NEW(新数据)
-- DELETE事件:仅用OLD(旧数据)
-- UPDATE事件:可用OLD(更新前)、NEW(更新后)
业务SQL语句;
END;
18.4.1、核心参数说明
|
参数项 |
取值 / 规则 |
|---|---|
|
触发器名 |
需在当前数据库唯一;若指定数据库,格式为 |
|
触发器时机 |
|
|
触发器事件 |
|
|
表名 |
必须是永久表(不能是临时表 / 视图),触发器与该表绑定 |
|
|
行级触发器,每操作一行数据触发一次(MySQL 强制要求) |
18.5、触发器使用注意事项
1. 创建限制
-
同一个表不能有相同时机 + 相同事件的触发器(如一个表不能同时有 2 个
BEFORE UPDATE触发器); -
触发器不能与临时表、视图关联,只能绑定永久表。
2. 逻辑限制
-
不能调用返回数据到客户端的存储过程,不能使用
CALL的动态 SQL; -
不能使用事务语句(
START TRANSACTION/COMMIT/ROLLBACK); -
避免包含返回结果的
SELECT语句(会向应用程序返回数据,需特殊处理)。
3. 性能与原子性
-
行级触发器处理大数据集时效率较低(每一行都触发);
-
MyISAM 引擎下,触发器不保证原子性(触发器执行失败时,已执行的表操作不会回滚)。
4. 维护注意
-
数据库索引 / 统计信息更新后,需重新编译触发器(以保持查询优化效率);
-
触发器中避免复杂 / 耗时操作(会影响原数据操作的性能)。
18.6、INSERT
创建触发器 1:自动计算入库金额(BEFORE INSERT)
CREATE TRIGGER calc_entry_volume
BEFORE INSERT ON Entry
FOR EACH ROW
BEGIN
-- 自动计算入库金额:入库数量 × 入库单价
SET NEW.Volume = NEW.Amount * NEW.Price;
END;
创建触发器 2:自动更新库存表(AFTER INSERT)
产品入库后,自动更新库存表的 “库存数量” 和 “加权平均单价”:
CREATE TRIGGER update_storage_after_entry
AFTER INSERT ON Entry
FOR EACH ROW
BEGIN
-- 情况1:产品已存在库存 → 更新库存数量+加权平均单价
IF EXISTS (SELECT 1 FROM Storage WHERE ProductID = NEW.ProductID) THEN
UPDATE Storage
SET
-- 加权平均单价公式:(原库存金额 + 新入库金额) / (原库存数量 + 新入库数量)
Price = (Storage.Price * Storage.Amount + NEW.Volume) / (Storage.Amount + NEW.Amount),
-- 库存数量 = 原数量 + 入库数量
Amount = Storage.Amount + NEW.Amount
WHERE ProductID = NEW.ProductID;
-- 情况2:产品首次入库 → 新增库存记录
ELSE
INSERT INTO Storage (ProductID, Amount, Price)
VALUES (NEW.ProductID, NEW.Amount, NEW.Price);
END IF;
END;
18.7、UPDATE
2312

被折叠的 条评论
为什么被折叠?



