一、存储过程
1. 定义
一组为完成特定功能的 SQL 语句集,经编译后存储在数据库中,用户通过指定名称和参数执行并获取结果。
2. 特点
- 封装性:业务逻辑封装在数据库内部,降低应用程序复杂度
- 可维护性:集中管理数据库操作,便于更新维护
- 可重用性:可多次调用,提升代码复用率
3. 优缺点
表格
| 优点 | 缺点 |
|---|---|
| 编译后存储,执行速度更快 | 跨数据库移植性差 |
| 代码可重用,减少重复编写 | 调试工具支持少,开发维护困难 |
| 限制直接表访问,提升安全性 | 高并发场景增加数据库压力 |
| 可实现复杂事务逻辑 | - |
| 表结构变更时,仅需修改存储过程,降低耦合 | - |
4. 核心语法
(1)创建
sql
DELIMITER // -- 修改结束标识符
CREATE PROCEDURE 存储过程名 (参数列表)
BEGIN
-- SQL语句
END //
DELIMITER ; -- 恢复默认结束标识符
(2)调用
sql
CALL 存储过程名 (参数列表);
(3)查看
sql
-- 查看指定数据库的所有存储过程
SELECT * FROM information_schema.ROUTINES WHERE ROUTINE_SCHEMA = '数据库名';
-- 查看单个存储过程的定义
SHOW CREATE PROCEDURE 存储过程名;
(4)删除
sql
DROP PROCEDURE [IF EXISTS] 存储过程名;
5. 简单示例
计算学生总分
sql
CREATE PROCEDURE p_calAvg()
BEGIN
select name, chinese + math + english as total from exam;
END;
CALL p_calAvg();
二、MySQL 变量
分为系统变量 、用户自定义变量 、局部变量三类,核心区别在于作用域和声明方式。
1. 系统变量
MySQL 服务器的配置变量,控制服务器行为和性能,分全局(GLOBAL)和会话(SESSION)。
(1)查看
sql
-- 查看所有/指定系统变量
SHOW [GLOBAL|SESSION] VARIABLES;
SHOW [GLOBAL|SESSION] VARIABLES LIKE '变量名%';
-- 用SELECT查看
SELECT @@[GLOBAL|SESSION].系统变量名;
(2)设置
sql
SET [GLOBAL|SESSION] 系统变量名 = 值;
SET @@SESSION.系统变量名 = 值; -- 默认为SESSION
注意:会话变量随会话关闭失效,全局变量随 MySQL 重启失效,永久生效需修改配置文件。
2. 用户自定义变量
SQL 会话中定义,无需提前声明,作用域为当前会话。
(1)赋值(推荐:=,避免与等号判断冲突)
sql
SET @var_name := 表达式;
SELECT 列名 INTO @var_name FROM 表名 WHERE 条件;
(2)使用
sql
SELECT @var_name;
3. 局部变量
仅在存储过程、函数、触发器内有效,必须用 DECLARE 声明,作用域为 BEGIN...END 块内。
(1)声明
sql
DECLARE 变量名 变量类型 [DEFAULT 默认值];
(2)赋值
sql
SET var_name := 值;
SELECT 列名 INTO var_name FROM 表名 WHERE 条件;
4. 注意事项
- 变量名不区分大小写
- 局部变量必须先声明后使用,自定义变量无需声明
- 自定义变量会话结束失效,局部变量随存储过程 / 函数结束失效
- 避免使用 MySQL 保留字作为变量名
三、SQL 编程语法
1. 条件判断 - IF 语句
sql
IF 条件1 THEN
执行语句;
ELSEIF 条件2 THEN
执行语句;
ELSE
执行语句;
END IF;
2. 存储过程参数
分三类,用于实现参数的传入和返回,默认类型为 IN。
表格
| 类型 | 描述 |
|---|---|
| IN | 输入参数,调用时传入值 |
| OUT | 输出参数,作为存储过程返回值 |
| INOUT | 输入输出参数,既传值又返回结果 |
语法
sql
CREATE PROCEDURE 存储过程名 (IN 参1 类型, OUT 参2 类型, INOUT 参3 类型)
BEGIN
-- SQL语句
END;
3. 分支选择 - CASE 语句
语法 1:值匹配
sql
CASE 匹配值
WHEN 目标值1 THEN 执行语句;
WHEN 目标值2 THEN 执行语句;
ELSE 执行语句;
END CASE;
语法 2:条件匹配
sql
CASE
WHEN 条件1 THEN 执行语句;
WHEN 条件2 THEN 执行语句;
ELSE 执行语句;
END CASE;
注意:无匹配项且无 ELSE 会报错,执行语句不能为空可加 BEGIN...END。
4. 循环语句
MySQL 支持 WHILE、REPEAT、LOOP 三种循环,LOOP 可配合 LEAVE(break)、ITERATE(continue)使用。
(1)WHILE(先判断后执行)
sql
WHILE 条件 DO
执行语句;
END WHILE;
(2)REPEAT(先执行后判断,至少执行 1 次)
sql
REPEAT
执行语句;
UNTIL 条件 -- 无分号
END REPEAT;
(3)LOOP(自定义循环,需手动退出)
sql
标签名: LOOP
IF 退出条件 THEN
LEAVE 标签名; -- 退出整个循环
END IF;
IF 跳过条件 THEN
ITERATE 标签名; -- 跳过本次循环
END IF;
执行语句;
END LOOP 标签名;
5. 游标
用于在存储过程 / 函数中逐行检索查询结果集 ,MySQL 中游标只读,不能更新。
核心语法(声明→打开→获取→关闭)
sql
-- 声明(需在变量后、条件处理程序前)
DECLARE 游标名 CURSOR FOR 查询语句;
-- 打开
OPEN 游标名;
-- 逐行获取结果到变量
FETCH 游标名 INTO 变量1, 变量2,...;
-- 关闭
CLOSE 游标名;
问题 :游标遍历完后继续获取会报 1329 错误,需配合条件处理程序解决。
6. 条件处理程序
用于捕获程序执行中的错误 / 警告,定义处理方式,避免程序异常停止。
语法
sql
DECLARE 处理方式 HANDLER FOR 异常类型 [,异常类型...] 执行语句;
-- 处理方式:CONTINUE(继续执行)、EXIT(终止执行)
-- 异常类型:MYSQL错误码/ SQLSTATE值/ SQLWARNING/ NOT FOUND/ SQLEXCEPTION
常用 :DECLARE CONTINUE HANDLER FOR NOT FOUND SET 标识变量 := TRUE;(捕获游标遍历完成的 NOT FOUND 异常)
7. 存储函数
有返回值的存储过程,参数仅支持 IN 类型,类似 MySQL 内置函数。
语法(MySQL8.0 需指定 characteristic,binlog 开启时必加)
sql
CREATE FUNCTION 函数名 (参数列表)
RETURNS 返回值类型 [DETERMINISTIC/NO SQL/READS SQL DATA/MODIFIES SQL DATA]
BEGIN
-- SQL语句
RETURN 返回值;
END;
-- 调用
SELECT 函数名 (参数列表);
characteristic 说明
- DETERMINISTIC:相同输入返回相同结果
- NO SQL:无 SQL 语句
- READS SQL DATA:仅读取数据
- MODIFIES SQL DATA:修改数据
8. 存储过程与存储函数的核心区别
表格
| 存储过程 | 存储函数 |
|---|---|
| 可有可无返回值 | 必须有返回值,且仅一个 |
| 支持 IN/OUT/INOUT 三种参数 | 仅支持 IN 类型参数 |
| 用 CALL 调用 | 用 SELECT 调用 |
| 可实现复杂业务逻辑、事务 | 适合简单的计算 / 查询,返回单一结果 |
四、触发器
1. 定义
与表关联的数据库对象,对表执行INSERT/UPDATE/DELETE 时自动触发执行指定 SQL,MySQL仅支持行级触发器(每操作一行触发一次),不支持语句级触发器。
2. 核心关键字
用于引用变更的记录数据,不同触发器类型可用关键字不同。
表格
| 触发器类型 | 可用关键字 | 关键字含义 |
|---|---|---|
| INSERT | NEW | 新增 / 将要新增的数据 |
| UPDATE | OLD/NEW | OLD = 修改前数据,NEW = 修改后数据 |
| DELETE | OLD | 删除 / 将要删除的数据 |
3. 触发时间
- BEFORE:操作表之前触发(用于数据验证、预处理)
- AFTER:操作表之后触发(用于日志记录、数据同步)
4. 核心语法
(1)创建
sql
DELIMITER //
CREATE TRIGGER [IF NOT EXISTS] 触发器名
触发时间 BEFORE/AFTER 触发事件 INSERT/UPDATE/DELETE
ON 表名 FOR EACH ROW -- 行级触发器标识
BEGIN
触发执行的SQL语句;
END //
DELIMITER ;
(2)查看
sql
SHOW TRIGGERS;
(3)删除
sql
DROP TRIGGER [IF EXISTS] [数据库名.]触发器名;
5. 典型场景
数据变更日志记录、数据一致性校验、关联表自动更新。
综合案例:学生信息管理系统(含存储过程 + 变量 + 游标 + 条件处理程序 + 存储函数 + 触发器)
案例需求
- 实现学生分数等级判定、1-n 累加计算的存储过程 / 函数
- 遍历指定班级学生,将信息同步到新表(游标 + 条件处理程序)
- 对学生表的增删改操作做日志记录(触发器)
前提准备:创建基础表
sql
-- 班级表
CREATE TABLE IF NOT EXISTS class (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL COMMENT '班级名'
);
-- 学生表
CREATE TABLE IF NOT EXISTS student (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(20) NOT NULL,
sno VARCHAR(20) UNIQUE NOT NULL,
age INT,
gender TINYINT COMMENT '1=男,2=女',
enroll_date DATE,
class_id INT,
FOREIGN KEY (class_id) REFERENCES class(id)
);
-- 学生成绩表
CREATE TABLE IF NOT EXISTS exam (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
student_id BIGINT,
chinese INT,
math INT,
english INT,
FOREIGN KEY (student_id) REFERENCES student(id)
);
-- 插入测试数据
INSERT INTO class (name) VALUES ('Java一班'), ('Python二班'), ('大数据三班');
INSERT INTO student (name, sno, age, gender, enroll_date, class_id)
VALUES ('唐三藏', '100001', 20, 1, '2024-09-01', 1),
('孙悟空', '100002', 19, 1, '2024-09-01', 1),
('曹操', '300001', 28, 1, '2024-09-01', 3);
INSERT INTO exam (student_id, chinese, math, english)
VALUES (1, 88, 95, 90), (2, 92, 89, 85), (3, 78, 82, 80);
1. 存储过程:判定学生分数等级(IN+OUT 参数)
sql
DELIMITER //
CREATE PROCEDURE p_get_score_level(IN score INT, OUT level VARCHAR(10))
BEGIN
IF score >= 90 THEN
SET level := '优秀';
ELSEIF score >= 80 THEN
SET level := '良好';
ELSEIF score >= 60 THEN
SET level := '及格';
ELSE
SET level := '不及格';
END IF;
END //
DELIMITER ;
-- 调用
CALL p_get_score_level(88, @level);
SELECT @level; -- 结果:良好
2. 存储函数:计算 1-n 的累加和(带 characteristic)
sql
DELIMITER //
CREATE FUNCTION fun_sum_n(n INT)
RETURNS INT DETERMINISTIC
BEGIN
DECLARE total INT DEFAULT 0;
WHILE n > 0 DO
SET total := total + n;
SET n := n - 1;
END WHILE;
RETURN total;
END //
DELIMITER ;
-- 调用
SELECT fun_sum_n(100); -- 结果:5050
3. 存储过程:游标 + 条件处理程序,同步指定班级学生到新表
sql
DELIMITER //
CREATE PROCEDURE p_sync_student(IN c_id INT)
BEGIN
-- 声明局部变量
DECLARE s_name VARCHAR(20);
DECLARE c_name VARCHAR(50);
DECLARE is_done BOOLEAN DEFAULT FALSE;
-- 声明游标
DECLARE s_cursor CURSOR FOR
SELECT s.name, c.name FROM student s
JOIN class c ON s.class_id = c.id
WHERE s.class_id = c_id;
-- 条件处理程序:捕获游标遍历完成
DECLARE CONTINUE HANDLER FOR NOT FOUND SET is_done := TRUE;
-- 创建同步表
DROP TABLE IF EXISTS t_student_sync;
CREATE TABLE IF NOT EXISTS t_student_sync (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
student_name VARCHAR(20),
class_name VARCHAR(50)
);
-- 打开游标并遍历
OPEN s_cursor;
read_loop: LOOP
FETCH s_cursor INTO s_name, c_name;
IF is_done THEN
LEAVE read_loop;
END IF;
-- 插入同步数据
INSERT INTO t_student_sync (student_name, class_name) VALUES (s_name, c_name);
END LOOP read_loop;
-- 关闭游标
CLOSE s_cursor;
-- 查询同步结果
SELECT * FROM t_student_sync;
END //
DELIMITER ;
-- 调用:同步Java一班(class_id=1)学生
CALL p_sync_student(1);
4. 触发器:学生表增删改操作日志记录
(1)创建日志表
sql
CREATE TABLE IF NOT EXISTS student_log (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
operation_type VARCHAR(10) NOT NULL COMMENT 'insert/update/delete',
operation_time DATETIME NOT NULL DEFAULT NOW(),
operation_id BIGINT NOT NULL COMMENT '操作的学生ID',
operation_data VARCHAR(500) COMMENT '操作数据'
);
(2)创建 INSERT 触发器
sql
DELIMITER //
CREATE TRIGGER trg_student_insert
AFTER INSERT ON student FOR EACH ROW
BEGIN
INSERT INTO student_log (operation_type, operation_id, operation_data)
VALUES (
'insert',
NEW.id,
CONCAT(NEW.id, ',', NEW.name, ',', NEW.sno, ',', NEW.age, ',', NEW.gender, ',', NEW.enroll_date, ',', NEW.class_id)
);
END //
DELIMITER ;
(3)创建 UPDATE 触发器
sql
DELIMITER //
CREATE TRIGGER trg_student_update
AFTER UPDATE ON student FOR EACH ROW
BEGIN
INSERT INTO student_log (operation_type, operation_id, operation_data)
VALUES (
'update',
NEW.id,
CONCAT('旧数据:', OLD.id, ',', OLD.name, ',', OLD.sno, ',', OLD.age, ' | 新数据:', NEW.id, ',', NEW.name, ',', NEW.sno, ',', NEW.age)
);
END //
DELIMITER ;
(4)创建 DELETE 触发器
sql
DELIMITER //
CREATE TRIGGER trg_student_delete
AFTER DELETE ON student FOR EACH ROW
BEGIN
INSERT INTO student_log (operation_type, operation_id, operation_data)
VALUES (
'delete',
OLD.id,
CONCAT(OLD.id, ',', OLD.name, ',', OLD.sno, ',', OLD.age, ',', OLD.gender)
);
END //
DELIMITER ;
(5)测试触发器
sql
-- 插入学生
INSERT INTO student (name, sno, age, gender, enroll_date, class_id)
VALUES ('刘备', '300002', 27, 1, '2024-09-01', 3);
-- 更新学生
UPDATE student SET age = 26 WHERE name = '刘备';
-- 删除学生
DELETE FROM student WHERE name = '刘备';
-- 查看日志
SELECT * FROM student_log;
5. 存储过程:计算学生总分并判定等级(综合变量 + 查询)
sql
DELIMITER //
CREATE PROCEDURE p_student_total_score(IN s_id BIGINT)
BEGIN
DECLARE total INT DEFAULT 0;
DECLARE level VARCHAR(10);
-- 查询学生总分
SELECT chinese + math + english INTO total FROM exam WHERE student_id = s_id;
-- 调用分数等级存储过程
CALL p_get_score_level(total, level);
-- 查询结果
SELECT s.name, total AS 总分, level AS 等级
FROM student s WHERE s.id = s_id;
END //
DELIMITER ;
-- 调用:查询唐三藏(id=1)的总分和等级
CALL p_student_total_score(1);