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.如何查看数据库中创建的触发器

相关推荐
alexhilton36 分钟前
将应用迁移到Navigation 3:痛点、加班和紧急修复
android·kotlin·android jetpack
这个DBA有点耶5 小时前
NULL不是空——数据库里最反直觉的设计,90%新人踩过的坑
数据库·mysql·代码规范
杉氧6 小时前
Navigation Compose 深度实践:如何优雅地串联起你的全栈 App?
android·架构·android jetpack
这个DBA有点耶7 小时前
AI写的SQL跑崩了生产库,这锅谁背?
数据库·人工智能·程序员
镜舟科技7 小时前
Databricks 再提 LTAP,AI 时代的数据底座为何重回大一统叙事?
数据库·架构·agent
Databend8 小时前
从湖仓升级为 Agent 时代的数据控制面,Snowflake 和 Databricks 有哪些布局
大数据·数据库·agent
雨白10 小时前
指针与数组的核心机制
android
ClouGence11 小时前
SQL Server CDC 能放到 Always On 备库读吗?一文讲透原理与实践
数据库·sql server
黄林晴14 小时前
Room 3.0 正式发布!包名彻底重构,KMP 成为核心主线
android·android jetpack
三少爷的鞋15 小时前
Kotlin 协程环境下的 DCL 懒加载:别把线程时代的经验直接搬过来
android