存储过程
存储过程是什么
存储过程是一组为了完成特定功能的SQL语句集,经过编译后存储在数据库中,用户通过制定存储过程的名字和参数来执行,获取相应的结果。
特点
- 封装性:将业务逻辑封装在数据库的内部,减少应用程序的复杂性
- 可维护性:集中管理数据库的操作,便于维护和更新
- 可重用性 :可以被多次调用,提高代码的重用性

优缺点
优点
- 性能优化:存储过程在创建时编译并存储在数据库中,值性能速度比单个SQL语句快。
- 代码重用:存储过程可以重复调用,减少重复代码,提高代码的可维护性
- 安全性:可以限制用户直接访问数据库,通过存储过程间接访问,从而保证系统安全性
- 事务管理:可以在存储过程中实现复杂的事务逻辑
- 降低耦合:当表结构发生变化之后,只需要修改对应的存储过程,应用程序的改动较小
缺点
- 可移植性差:存储过程不能跨数据库一致,更换数据库时需要重新编写
- 调试困难:只有少数数据库管理系统支持存储过程的调试,开发和维护困难。
- 不适合高并发场景:在高并发场景下,存储过程会增加数据库的压力,难以维护。
语法
创建
sql
-- 修改SQL语句结束标识符为 //
DELIMITER //
-- 创建存储过程
CREATE PROCEDURE 存储过程名 (参数列表)
BEGIN
-- SQL 语句
END //
-- 修改SQL语句结束标识符为 ;
DELIMITER ;
调用
sql
-- 调⽤存储过程
CALL 存储过程名 (参数列表);
查看
sql
-- 查看指定数据库中创建的存储过程
SELECT * FROM information_schema.ROUTINES WHERE ROUTINE_SCHEMA = '数据库名';
-- 查看存储过程的定义
SHOW CREATE PROCEDURE 存储过程名;
删除
sql
DROP PROCEDURE [IF EXISTS] 存储过程名;
变量
在MySQL中可以分成三个变量:系统变量、用户自定义变量以及局部变量。
系统变量
系统变量 是MySQL的配置变量,控制服务器的行为和性能。分为全局变量(global)和会话变量(session)
查看系统变量
sql
-- 查看所有系统变量
SHOW [GLOBAL|SESSION] VARIABLES;
-- 查看指定的系统变量
SHOW [GLOBAL|SESSION] VARIABLES LIKE 'xxx';
-- 查看指定的系统变量,可以通过LKIE进⾏模糊查询
SHOW [GLOBAL|SESSION] VARIABLES like '%xxx%';
-- 使⽤SELECT查看指定系统变量
SELECT @@[GLOBAL|SESSION].系统变量名;
示例
sql
-- 示例:查看以auto开头的全局系统变量
show global variables like 'auto%';
-- ⽰例:查看事务⾃动提交全局系统变量
select @@global.autocommit;
设置系统变量
sql
SET [GLOBAL|SESSION] 系统变量名 = 值;
SET @@SESSION.系统变量名 = 值;
示例
sql
-- 示例:设置事务自动提交会话变量为关闭/开启
set @@session.autocommit = 1;
set session auto commit = 1;
- 如果没有指定[global session],默认就是会话全局变量
- 会话关闭之后,设置的会话全局变量会失效;新建会话会读取并使用全局系统变量
- MySQL重启后,设置的全局(GLOBAL)变量失效,如果想使全局系统变量永久⽣效,需要修改选项⽂件
用户自定义变量
用户自定义变量 是在SQL中定义的变量,不用提前声明,作用域为当前会话
赋值
sql
-- ⽅式⼀
SET @var_name = expr [, @var_name]...;
-- ⽅式⼆ 【推荐】
SET @var_name := expr [, @var_name]...;
-- ⽅式三:在SELECT语句中
SELECT @var_name := expr [, @var_name]...;
-- ⽅式四:查询结果赋值给⾃定义变量
SELECT 列名 INTO @var_name FROM 表名 WHERE ...;
示例
sql
set @age := 20;
select @age;
# 赋值 + 查看
select @age := 21;
select count(*) into @stu_count from student;
select @stu_count;
由于在SQL中比较相等也是使用=,所以在为变量赋值的时候推荐使用(:=)
局部变量
局部变量 只在存储过程,函数或触发器的范围内有效。需要使用DECLARE声明,作用域的范围在声明的BEGIN END块内。
声明
- 变量可以是任何有效的MySQL数据类型,如
INT,VARCHAR,DETETIME等
sql
DECLARE 变量名 变量类型 [DEFAULT 默认值] ...;
赋值
sql
-- 方式一
SET var_name = 值;
-- ⽅式⼆ 【推荐】
SET var_name := 值;
-- ⽅式三:查询结果赋值给⾃定义变量
SELECT 列名 INTO var_name FROM 表名 WHERE ...;
示例
sql
delimiter //
# 定义存储过程
create procedure p1()
begin
-- 定义局部变量
declare stu_count int default 0;
-- 把查询的记过赋值给局部变量
select count(*) into stu_count from student;
-- 打印局部变量
select stu_count;
end //
delimiter ;
call p1();
select count(*) from student;
注意事项
- 变量名不区分大小写
- 在存储过程和函数中,局部变量必须要使用前初始化
- 用户自定义变量会在会话结束之后失效,而局部变量在存储过程或函数结束时失效
- 避免使用保留字作为变量名
SQL编程
结构化查询语言(Structured Query Language)简称SQL ,是一种特殊目的的编程语言,是一种数据库查询和程序设计语言,用于存取数据以及查询、更新和管理关系数据库系统。
条件判断 - IF语句
语法
sql
IF 条件1 THEN
......
[ELSEIF 条件2 THEN
......
ELSE
......]
END IF;
示例
sql
# 分数 >= 90 分等级为优秀
# 分数 >= 80 且分数 < 90分等级为良好
# 分数 >= 60 分且分数 < 80分等级为及格
# 分数 < 60分等级为不及格
delimiter //
create procedure p2()
begin
-- 定义初始分数变量
declare score int default 86;
-- 定义一个结果变量
declare result varchar(10);
-- 判断
if score >= 90 then
set result := '优秀';
elseif score>=80 then
set result := '良好';
elseif score>=60 then
set result := '及格';
else
set result := '不及格';
end if;
-- 查询结果
select result;
end //
delimiter ;
call p2();
参数
| 类型 | 描述 |
|---|---|
| IN | 输入类型,调用存储过程中要输入的值,默认参数类型 |
| OUT | 输出类型,可以作为存储过程的返回值 |
| INOUT | 输入输出类型,既可以作为输入类型也可以作为输出类型 |
sql
-- 修改SQL语句结束标识符为 //
DELIMITER //
-- 创建存储过程
CREATE PROCEDURE 存储过程名 ([IN/OUT/INOUT 参数名 参数类型] [,...])
BEGIN
-- SQL 语句
END //
-- 修改SQL语句结束标识符为 ;
DELIMITER ;
示例1:传⼊⼀个分数的值,判定当前分数对应的等级
◦ 分数 >= 90 分等级为优秀
◦ 分数 >= 80 且分数 < 90分等级为良好
◦ 分数 >= 60 分且分数 < 80分等级为及格
◦ 分数 < 60分等级为不及格
sql
delimiter //
create procedure p3(in score int,out result varchar(10))
begin
-- 判断
if score >= 90 then
set result := '优秀';
elseif score>=80 then
set result := '良好';
elseif score>=60 then
set result := '及格';
else
set result := '不及格';
end if;
end //
delimiter ;
call p3 (28,@result);
select @result;
示例2:传⼊⼀个分数的值,在传⼊分数的基础上加10分,然后返回
sql
delimiter //
create procedure p4(inout score int)
begin
set score := score + 10;
end //
delimiter ;
set @result := 27;
call p4(@result);
select @result;
CASE
语法一
sql
-- 语法⼀
CASE case_value
WHEN when_value THEN statement_list
[WHEN when_value THEN statement_list] ...
[ELSE statement_list]
END CASE
语法二
sql
-- 语法⼆
CASE
WHEN search_condition THEN statement_list
[WHEN search_condition THEN statement_list] ...
[ELSE statement_list]
END CASE
示例
sql
delimiter //
create procedure p5(in code int,out result varchar(50))
begin
case code
when 0 then
set result := '成功';
when 10001 then
set result := '用户名或者密码错误';
else
set result := '未知,请联系管理员';
end case;
end //
delimiter ;
call p5(-1,@result);
select @result;
-------------------------------------------
delimiter //
create procedure p6(in month int,out result varchar(50))
begin
case
when month>=1 and month <= 3 then
set result := '第一季度';
when month >= 4 and month <= 6 then
set result := '第二季度';
else
set result := '未知';
end case;
end //
delimiter ;
call p6(-1,@result);
select @result;
循环
WHILE
语法
sql
WHILE search_condition DO
statement_list
END WHILE;
示例
sql
-- 传入一个n,求1到n的和
delimiter //
create procedure p7 (in n int)
begin
declare total int default 0;
while n>0 do
set total = total + n;
set n = n-1;
end while ;
select total;
end //
delimiter ;
call p7(10);
REPEAT
语法
sql
REPEAT
statement_list
UNTIL search_condition
END REPEAT;
示例
sql
delimiter //
create procedure p8(in n int)
begin
declare total int default 0;
repeat
set total = total + n;
set n = n - 1;
until n<=0 end repeat ;
select total;
end //
delimiter ;
drop procedure p8;
call p8(-1);
LOOP
语法
sql
[begin_label:] LOOP
statement_list
END LOOP [end_label]
示例
sql
delimiter //
create procedure p9 (in n int)
begin
declare total int default 0;
sum_label:loop
if n<=0 then
leave sum_label;
end if;
set total = total + n;
set n = n - 1;
end loop sum_label;
select total;
end //
delimiter ;
call p9(10);
游标
- MySQL中的额游标是一种数据库对象,允许存储过程和函数 中对查询到的结果集进行逐行检索
MySQL的游标是只读的,不能进行更新操作
语法
- 使用游标之前不许声明游标,之后使用
OPEN、FETCH和CLOSSE语句来打开游标、获取游标记录和关闭游标。 - 游标 必须在条件处理程序 之前被声明,变量 必须在游标或条件变量之前被声明。
sql
-- 声明游标
DECLARE 游标名 CURSOR FOR 查询语句;
-- 打开游标
OPEN 游标名;
-- 获取游标记录
FETCH 游标名 INTO 变量[, 变量] ...;
-- 关闭游标
CLOSE 游标名;
示例
传入班级编号,查询学生表中属于该班级的学生信息
sql
delimiter //
create procedure p11(in class_id int)
begin
declare student_name varchar(20);
declare class_name varchar(20);
declare s_cursor cursor for
select s.name,c.name from student s,class c
where s.class_id = c.id and s.class_id = class_id;
-- 创建新表
drop table if exists t_student_class;
create table if not exists t_student_class(
id bigint primary key auto_increment,
student_name varchar(20) not null,
class_name varchar(20) not null
);
-- 开启游标
open s_cursor;
-- 遍历结果集合
while true do
-- 获取游标对应的记录
fetch s_cursor into student_name,class_name;
-- 写入到新表中
insert into t_student_class values(null,student_name,class_name);
end while;
-- 关闭游标
close s_cursor;
end //
delimiter ;
call p11(1);
由于while循环的退出条件是true,此时是⼀个死循环,当游标遍历完成之后继续向后遍历,发现没有记录,所以报错,可以通过条件处理程序解决
条件处理程序
- 定义条件是事先定义程序指定过程中可能出现的问题
- 处理程序定义了遇到问题时应当采取的处理方式
- 使用条件处理程序 保证存储过程或者函数在遇到警告或者错误能继续执行,可以增强程序处理问题的能力,避免程序异常停止运行
语法
sql
DECLARE handler_action HANDLER
FOR condition_value [, condition_value] ...
statement
handler_action: {
CONTINUE -- 继续执⾏当前程序
| EXIT -- 终⽌执⾏当前程序
}
condition_value: {
mysql_error_code -- MYSQL错误码
| SQLSTATE [VALUE] sqlstate_value -- 状态码
| SQLWARNING -- 所有以01开头的SQLSTATE代码
| NOT FOUND -- 所有以02开头的SQLSTATE代码
| SQLEXCEPTION -- 所有没有被SQLWARNING或NOT FOUND捕获的SQLSTATE代码
}
加入添加处理程序,处理游标越界问题
sql
delimiter //
create procedure p12(in class_id int)
begin
declare student_name varchar(20);
declare class_name varchar(20);
-- 定义游标的结束表示
declare is_done bool default false;
declare s_cursor cursor for
select s.name,c.name from student s,class c
where s.class_id = c.id and s.class_id = class_id;
-- 定义条件处理程序
declare continue handler for not found
set is_done := true;
-- 创建新表
drop table if exists t_student_class;
create table if not exists t_student_class(
id bigint primary key auto_increment,
student_name varchar(20) not null,
class_name varchar(20) not null
);
-- 开启游标
open s_cursor;
read_loop:loop
-- 获取游标对应的记录
fetch s_cursor into student_name,class_name;
if is_done then
leave read_loop;
end if;
-- 写入到新表中
insert into t_student_class values(null,student_name,class_name);
end loop read_loop;
# -- 遍历结果集合
# while not is_done do
# -- 获取游标对应的记录
# fetch s_cursor into student_name,class_name;
# -- 写入到新表中
# insert into t_student_class values(null,student_name,class_name);
# end while;
-- 关闭游标
close s_cursor;
end //
delimiter ;
call p12(1);
select * from t_student_class;
触发器
触发器是什么
- 触发器是一个与表关联的数据库对象,在对表进行insert、update、delete操作时,触发并制定定义触发器时指定的SQL语句
- 触发器可以在对表操作之前 或者之后 执行,这叫做触发时间
- 触发器可以执行SQL语句或者逻辑块,实现复杂的业务逻辑和数据验证
- MySQL支持三种类型的触发器:insert触发器,update触发器,delete触发器 。使用
old和new关键字来引用触发器中发生变化的记录内容。
| 触发器类型 | OLD 和 NEW |
|---|---|
| INSERT 触发器 | NEW 表示将要或已经新增的数据 |
| UPDATE 触发器 | OLD 表示修改之前的数据,NEW 表示将要或已经修改的数据 |
| DELETE 触发器 | OLD 表示将要或已经删除的数据 |
行级触发器和语句级触发器:
- 行级触发器:当对表中每一行进行insert、update、delete操作时,行级触发器对每一行都触发一次。行级触发器都可以访问受影响行的旧值和新值,常用于实现复杂的业务逻辑对新旧值的访问
- 语句级触发器:在整个insert、update、delete语句执行只触发一次。无论语句影响了多少行,语句级触发器都在语句开始或者结束的时候触发一次。语句级触发器主要用于实现一些全局性的操作,比如数据同步,数据清理
- MySQL只支持行触发器,不支持语句级触发器
语法
sql
-- 创建
CREATE TRIGGER [IF NOT EXISTS] trigger_name
trigger_time trigger_event
ON tbl_name FOR EACH ROW
BEGIN
trigger_stmt;
END;
trigger_time: { BEFORE | AFTER }
trigger_event: { INSERT | UPDATE | DELETE }
-- 查看
SHOW TRIGGERS;
-- 删除, 如果没有指定schema_name,默认为当前数据库
DROP TRIGGER [IF EXISTS] [schema_name.]trigger_name;
练习
- 通过触发器记录学生表的变更日志,将变更日志写入日志表student_log中,包含增加、修改和删除操作
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 comment '操作时间',
operation_id bigint not null comment '操作的记录ID',
operation_data varchar(500) comment '操作数据'
);
插入数据的触发器
sql
-- 1. 先修改语句分隔符为 //(避免与触发器内的 ; 冲突)
DELIMITER //
-- 2. 创建插入触发器(确保语法连续、字段匹配)
CREATE TRIGGER trg_student_insert
AFTER INSERT ON student
FOR EACH ROW
BEGIN
-- 插入操作日志到 student_log 表
INSERT INTO student_log (
operation_type,
operation_time,
operation_id,
operation_data
)
VALUES (
'insert', -- 操作类型
NOW(), -- 操作时间
NEW.id, -- 新增记录的ID
-- 拼接数据(用 CONCAT_WS 避免 NULL 导致整体为空)
CONCAT_WS(',', NEW.id, NEW.name, NEW.sno, NEW.age)
);
END //
-- 3. 恢复默认分隔符为 ;
DELIMITER ;
更新数据的触发器
sql
delimiter //
create trigger trg_student_update
after update on student for each row
begin
-- 插入日志到student_log表中
-- 插入操作日志到 student_log 表
INSERT INTO student_log (
operation_type,
operation_time,
operation_id,
operation_data
)
VALUES ('update',
NOW(),
NEW.id,
concat(OLD.id, ',', old.name, ',', old.sno, ',', OLD.age, ',', old.gender, ',', OLD.enroll_date, '|',
new.id, ',', new.name, ',', new.sno, ',', new.age, ',', new.gender, ',', new.enroll_date)
);
end //
delimiter ;
删除数据的触发器
sql
-- 删除数据的触发器
delimiter //
create trigger trg_student_delete
after delete on student for each row
begin
insert into student_log(
operation_type,
operation_time,
operation_id,
operation_data
)
values(
'delete',
now(),
old.id,
concat(old.id,',',old.enroll_date)
);
end //
delimiter ;