存储过程介绍
SQL 指令执行过程
从 SQL 执行的流程中分析存在的问题:
- 如果需要重复多次执行相同的 SQL, SQL 执行都需要通过连接传递到 MySQL, 并且需要经过编译和执行的步骤
- 如果需要连续执行多个 SQL 指令, 并且第二个 SQL 指令需要使用第一个 SQL 指令执行的结果作为参数
存储过程的介绍
存储过程:
将能够完成特定功能的 SQL 指令进行封装 (SQL 指令集), 编译之后存储在数据库服务器上, 并且为之取一个名字, 客户端可以通过名字直接调用这个 SQL 指令集, 获取执行结果
存储过程优缺点分析
存储过程优点
- SQL 指令无需客户端编写, 通过网络传送, 可以节省网络开销, 同时避免 SQL 指令在网络传输过程中被恶意篡改保证安全性
- 存储过程经过编译创建并保存在数据库中的, 执行过程无需重复的进行编译操作, 对 SQL 指令的执行过程进行了性能提升
- 存储过程中多个 SQL 指令之间存在逻辑关系, 支持流程控制语句 (分支、循环), 可以实现更为复杂的业务
存储过程的缺点
- 存储过程是根据不同的数据库进行编译、创建并存储在数据库中;当需要切换到其他的数据库产品时, 需要重新编写针对于新数据库的存储过程
- 存储过程受限于数据库产品, 如果需要高性能的优化会成为一个问题
- 在互联网项目中, 如果需要数据库的高 (连接) 并发访问, 使用存储过程会增加数据库的连接执行时间 (因为将复杂的业务交给了数据库进行处理)
创建存储过程
语法
create procedure <proc_name>([IN/OUT args])
begin
-- SQL
end;
示例
# 创建一个存储过程实现加法运算: Java语法中, 方法是有参数和返回值的
# 存储过程中, 是有输入参数和输出参数的
create procedure proc_test1(IN a int, IN b int, OUT c int)
begin
SET c = a + b;
end;
调用存储过程
# 定义变量 @m
set @m = 0;
# 调用存储过程, 将3传递给a, 将2传递给b, 将 @m 传递给c
call proc_test1(3, 2, @m);
# 显示变量值
select @m from dual;
存储过程中变量的使用
存储过程中的变量分为两种: 局部变量和用户变量
定义局部变量
局部变量: 定义在存储过程中的变量, 只能再存储过程内部使用
语法
# 局部变量要定义在存储过程中, ⽽且必须定义在存储过程开始
declare <attr_name> <type> [default value];
示例
create procedure proc_test2(IN a int,OUT r int)
begin
declare x int default 0; -- 定义 x int 类型, 默认值为 0
declare y int default 1; -- 定义 y
set x = a * a;
set y = a / 2;
set r = x + y;
end;
定义用户变量
用户变量: 相当于全局变量, 定义的用户变量可以通过 select @attrName from dual
进行查询
# 用户变量会存储在 MySQL 数据库的数据字典中 (dual)
# 用户变量定义使用 set 关键字直接定义, 变量名要以 @ 开头
set @n = 1;
给变量设置值
- 无论是局部变量还是用户变量, 都是使用
set
关键字修改值
set @n = 1;
call proc_test2(6, @n);
select @n from dual;
将查询结果赋值给变量
- 在存储过程中使用
select... into...
给变量赋值
# 查询学生数量
create procedure proc_test3(OUT c int)
begin
select count(stu_num) INTO c from students; --将查询到学生数量赋值给c
end;
# 调用存储过程
call proc_test3(@n);
select @n from dual;
用户变量使用注意事项
因为用户变量相当于全局变量, 可以在 SQL 指令以及多个存储过程中共享, 在开发中建议尽量少使用户变量, 用户变量过多会导致程序不易理解、难以维护
存储过程的参数
MySQL 存储过程的参数一共有三种: IN \ OUT \ INOUT
IN 输入参数
输入参数 —— 在调用存储过程中传递数据给存储过程的参数 (在调用的过程必须为具有实际值的变量或者字面值)
# 创建存储过程: 添加学生信息
create procedure proc_test4(IN snum char(8), IN sname varchar(20),
IN gender char(2), IN age int, IN cid int,
IN remark varchar(255))
begin
insert into students(stu_num, stu_name, stu_gender, stu_age, cid, remark)
values(snum, sname, gender, age, cid, remark);
end;
call proc_test4('20210108', '⼩丽', '⼥', 20, 1, 'aaa');
OUT 输出参数
输出参数 —— 将存储过程中产生的数据返回给过程调用者, 相当于 Java 方法的返回值, 但不同的是一个存储过程可以有多个输出参数
# 创建存储过程, 根据学⽣学号, 查询学⽣姓名
create procedure proc_test5(IN snum char(8), OUT sname varchar(20))
begin
select stu_name INTO sname from students where stu_num = snum;
end;
set @name = '';
call proc_test5('20210108', @name);
select @name from dual;
INOUT 输⼊输出参数
create procedure proc_test6(INOUT str varchar(20))
begin
select stu_name INTO str from students where stu_num = str;
end;
set @name = '20210108';
call proc_test6(@name);
select @name from dual;
存储过程中流程控制
在存储过程中支持流程控制语句用于实现逻辑的控制
分支语句
**if - then - else**
# 单分支: 如果条件成⽴, 则执行 SQL
if conditions then
-- SQL
end if;
# 如果参数 a 的值为 1, 则添加⼀条班级信息
create procedure proc_test7(IN a int)
begin
if a = 1 then
insert into classes(class_name, remark)
values('Java2109', 'test');
end if;
end;
# 双分支: 如果条件成⽴则执行 SQL1, 否则执行 SQL2
if conditions then
-- SQL1
else
-- SQL2
end if;
# 如果参数 a 的值为 1, 则添加⼀条班级信息; 否则添加⼀条学⽣信息
create procedure proc_test7(IN a int)
begin
if a = 1 then
insert into classes(class_name, remark)
values('Java2109', 'test');
else
insert into students(stu_num, stu_name, stu_gender, stu_age, cid, remark)
values('20210110', '⼩花', '⼥', 19, 1, '...');
end if;
end;
**case**
create procedure proc_test8(IN a int)
begin
case a
when 1 then
-- SQL1 如果 a 的值为 1, 则执行 SQL1
insert into classes(class_name, remark)
values('Java2110', '...');
when 2 then
-- SQL2 如果 a 的值为 2, 则执行 SQL2
insert into students(stu_num, stu_name, stu_gender, stu_age, cid, remark)
values('20210111', '⼩刚', '男', 21, 2, '...');
else
-- SQL (如果变量的值和所有 when 的值都不匹配, 则执行 else 中的 SQL)
update students set stu_age = 18 where stu_num = '20210110';
end case;
end;
循环语句
**while**
create procedure proc_test9(IN num int)
begin
declare i int;
set i = 0;
while i < num do
-- SQL
insert into classes(class_name, remark)
values(CONCAT('Java', i), '...');
set i = i + 1;
end while;
end;
call proc_test9(4);
**repeat**
create procedure proc_test10(IN num int)
begin
declare i int;
set i = 1;
repeat
-- SQL
insert into classes(class_name, remark)
values(CONCAT('Python', i),'...');
set i = i + 1;
until i > num end repeat;
end;
call proc_test10(4);
**loop**
create procedure proc_test11(IN num int)
begin
declare i int ;
set i = 0;
myLoop: loop
-- SQL
insert into classes(class_name, remark)
values(CONCAT('HTML', i), '...');
set i = i + 1;
if i = num then
leave myLoop;
end if;
end loop;
end;
call proc_test11(5);
存储过程管理
查询存储过程
存储过程是属于某个数据库的, 也就是说当存储过程创建在某个数据库之后, 只能在当前数据库中调用此存储过程
查询存储过程:查询某个数据库中有哪些存储过程
# 根据数据库名, 查询当前数据库中的存储过程
show procedure status where db = 'db_test2';
# 查询存储过程的创建细节
show create procedure db_test2.proc_test1;
修改存储过程
修改存储过程指的是修改存储过程的特征/特性
alter procedure <proc_name> 特征1 [特征2 特征3 ...]
存储过程的特征参数:
CONTAINS SQL
表示子程序包含 SQL 语句, 但不包含读或写数据的语句NO SQL
表示子程序中不包含 SQL 语句READS SQL DATA
表示子程序中包含读数据的语句MODIFIES SQL DATA
表示子程序中包含写数据的语句SQL SECURITY
{ DEFINER | INVOKER } 指明谁有权限来执行- DEFINER 表示只有定义者自己才能够执行
- INVOKER 表示调用者可以执行
COMMENT 'string'
表示注释信息
alter procedure proc_test1 READS SQL DATA;
删除存储过程
# drop 删除数据库中的对象 数据库、数据表、列、存储过程、视图、触发器、索引...
# delete 删除数据表中的数据
drop procedure proc_test1;
存储过程练习案例
使用存储过程解决企业项目开发过程中的问题
- 案例: 使用存储过程完成借书操作
数据准备
# 创建数据库
create database db_test3;
# 使用数据库
use db_test3;
# 创建图书信息表
create table books(
book_id int primary key auto_increment,
book_name varchar(50) not null,
book_author varchar(20) not null,
book_price decimal(10, 2) not null,
book_stock int not null,
book_desc varchar(200)
);
# 添加图书信息
insert into books(book_name, book_author, book_price, book_stock, book_desc)
values('Java程序设计', '亮亮', 38.80, 12, '亮亮老师带你学Java');
insert into books(book_name, book_author, book_price, book_stock, book_desc)
values('Java程序设计', '威哥', 44.40, 9, '千峰伟哥, Java王者领路');
# 创建学生信息表
create table students(
stu_num char(4) primary key,
stu_name varchar(20) not null,
stu_gender char(2) not null,
stu_age int not null
);
# 添加学生信息
insert into students(stu_num, stu_name, stu_gender, stu_age)
values('1001', '张三', '男', 20);
insert into students(stu_num, stu_name, stu_gender, stu_age)
values('1002', '李四', '女', 20);
insert into students(stu_num, stu_name, stu_gender, stu_age)
values('1003', '王五', '男', 20);
# 创建借书记录表
create table records(
rid int primary key auto_increment,
snum char(4) not null,
bid int not null,
borrow_num int not null,
is_return int not null, -- 0 表示未归还 1 表示已归还
borrow_date date not null,
constraint FK_RECORDS_STUDENTS foreign key(snum) references students(stu_num),
constraint FK_RECORDS_BOOKS foreign key(bid) references book(book_id)
);
创建存储过程实现借书业务
业务分析
哪个学生借哪本书, 借了多少本?
操作:
- 保存结束纪录
- 修改图书库存
条件:
- 判断学生是否存在
- 判段图书是否存在、库存是否充足
# a 输⼊参数 学号
# b 输⼊参数 图书编号
# m 输⼊参数 借书的数量
# state 输出参数 借书的状态 (1 借书成功, 2 学号不存在, 3 图书不存在, 4 库存不足)
create procedure proc_borrow_book(IN a char(4), IN b int, IN m int, OUT state int)
begin
declare stu_count int default 0;
declare book_count int default 0;
declare stock int default 0;
select count(stu_num) INTO stu_count from students where stu_num = a;
if stu_count > 0 then
select count(book_id) INTO book_count from books where book_id = b;
if book_count > 0 then
select book_stock INTO stock from books where book_id = b;
if stock >= m then
insert into records(snum, bid, borrow_num, is_return, borrow_date)
values(a, b, m, 0, sysdate());
update books set book_stock = stock - m where book_id = b;
set state = 1;
else
set state = 4;
end if;
else
set state = 3;
end if;
else
set state = 2;
end if;
end;
# 调用存储过程借书
set @state = 0;
call proc_borrow_book('1001', 1, 2, @state);
select @state from dual;
游标
如果要创建一个存储过程, 需要返回查询语句查询到的多条数据, 该如何实现?
游标的概念
游标可以用来依次取出查询结果集中的每一条数据——逐条读取查询结果集中的纪录
游标的使用步骤
# 1. 声明游标
declare mycursor cursor for select book_name, book_author, book_price from books;
# 2. 打开游标
open mycursor;
# 3. 使用游标: 提取游标当前指向的纪录 (提取之后, 游标自动下移)
fetch mycursor into bname, bauthro, bprice;
# 4. 关闭游标
close mycursor;
游标使用案例
create procedure proc_test2(OUT result varchar(200))
begin
declare bname varchar(20);
declare bauthor varchar(20);
declare bprice decimal(10, 2);
declare num int;
declare i int;
declare str varchar(50);
declare mycursor cursor for select book_name, book_author, book_price from books;
-- 此查询语句执行之后返回的是一个结果集 (多条记录), 使用游标可以来遍历查询结果集
select count(1) INTO num from books;
open mycursor;
set i = 0;
-- 使用游标要结合循环语句
while i < num do
-- 使用游标: 提取游标当前指向的纪录 (提取之后, 游标自动下移)
FETCH mycursor INTO bname, bauthor, bprice;
set i = i + 1;
-- set str = concat_ws('~', bname, bauthor, brpice);
select concat_ws('~', bname, bauthor, bprice) INTO str;
set result = concat_ws(',', result, str);
end while;
close mycursor;
end;
# 案例测试
set @r = '';
call proc_test2(@r);
select @r from dual;