MySQL的存储过程和触发器

存储过程

存储过程是什么

存储过程是一组为了完成特定功能的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数据类型,如INTVARCHARDETETIME
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的游标是只读的,不能进行更新操作

语法

  • 使用游标之前不许声明游标,之后使用OPENFETCHCLOSSE语句来打开游标、获取游标记录和关闭游标。
  • 游标 必须在条件处理程序 之前被声明,变量 必须在游标或条件变量之前被声明。
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触发器 。使用oldnew关键字来引用触发器中发生变化的记录内容。
触发器类型 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 ;

面试题

1. 存储过程的作用是什么?

2.如果创建一个存储过程

3.MySQL的变量都有哪几种

4.如何定义一个变量?

5.MySQL中使用变量是否需要提前声明

6.MySQL中的额参数分为哪几种

7. 用过游标吗?游标的作用是什么?

8.了解条件处理程序吗?介绍一下如何使用

9.存储函数和存储过程的区别是什么

10.如何查案数据库中创建的存储过程

11.什么是触发器

12.MySQL中的触发器分为几种类型?

13.行级触发器和语句级触发器的区别是什么?

14.说一下了解的触发器使用场景有哪些

15.如果对表中的数据进行更新,要在日志表中记录该条记录更新前与更新后的值,如何实现?

16.如何查看数据库中创建的触发器

相关推荐
壮哥_icon2 小时前
Android 使用 PackageInstaller 实现静默安装,并通过 BroadcastReceiver 自动重启应用
android·gitee·android-studio·android系统
WebCsDn_TDCode2 小时前
Android Studio使用教程
android·android studio
小蜜蜂嗡嗡2 小时前
Android studio配置忽略文件
android·ide·android studio
JIngJaneIL3 小时前
基于Java酒店管理系统(源码+数据库+文档)
java·开发语言·数据库·vue.js·spring boot
颜颜yan_3 小时前
DevUI自定义开发实践:从零开始构建自定义组件和插件
android·java·数据库
编织幻境的妖3 小时前
数据库隔离级别详解与选择
数据库
wljt3 小时前
达梦导入大数据
数据库
马克学长3 小时前
SSM物流系统h7fel(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面
数据库·ssm框架·物流管理系统
ZePingPingZe3 小时前
Spring Boot + MySQL读写分离实现方案
spring boot·mysql·adb