数据库存储过程

存储过程

sql语句执行的流程中分析存在的问题

1、如果我们需要重复多次执行相同的SQL语句,sql执行都需要通过链接传递到MySql

并且需要经过编译和执行的步骤

2、如果我们需要连续执行多个Sql命令,并且第二个Sql指令需要使用第一个sql指令执行的结果作 为参数


将能够完成特定功能的SQL指令进行封装,编译之后存储到数据库服务器上,并为之设置姓名

当客户端需要执行这个功能是,不用编写SQL指令,直接通过封装过的SQL指令的名字完成调用

即可

优点:

SQL指令无需客户端编写,通过网络传送,可以节省网络开销,同时避免SQL指令在网络传输中被恶意篡改

存储过程经过编译创建并保存在数据库中,执行过程中无需重复操作

存储过程中多个SQL指令之间存在逻辑关系,支持流程控制语句

缺点

存储过程是根据不同的数据库进行编译执行创建并存储在数据库中,当我们需要切换到其他数据库产品时,需要重新编写针对于新数据库的存储过程的处理

在互联网项目中,如果需要数据库的高并发访问时,使用存储过程会增加数据库连接响应时间

创建存储过程的语法

语法

 create procedure ([IN/OUT args])
 begin
   
 end;

实例

 -- 创建一个存储过程,实现加法运算
 -- 在存储过程中,是有输入参数和输出参数的
 create procedure proc_test01(IN a int, IN b int, OUT c int)
 begin
   SET c = a + b;    -- 设置c的值为a+b
 end;
 ​
 -- 调用存储过程
 -- 定义变量m
 SET @m = 0;
 -- 调用存储过程,将12,23,@m分别传递给a,b,c
 call proc_test01(12, 23, @m);
 -- 显示变量值
 -- dual为系统表,所定义的变量都存储在此表中
 select @m from dual;

变量的使用

存储过程中的变量分为:局部变量、用户变量、会话变量、全局变量

会话变量全局变量系统变量


MySQL存储过程中,定义变量有两种方式:

1、使用setselect直接赋值,变量名以@开头

 set @var=1; 

可以在一个会话的任何地方声明,作用域是整个会话,称为用户变量

2、以declare关键字声明的变量,只能在存储过程中使用,称为存储过程变量

 declare var1 int default 0;       -- 定义一个局部变量默认值为1

主要用在存储过程中,或者是给存储传参数中

两者的区别是:

在调用存储过程时,以declare声明的变量都会被初始化为null。而会话变量(即@开头的变量)则不会被再初始化,在一个会话内,只须初始化一次,之后在会话内都是对上一次计算的结果,就相当于在是这个会话内的全局变量。

一、局部变量,只在当前begin/end代码块中有效

局部变量一般用在sql语句块中,比如存储过程的begin/end。其作用域仅限于该语句块,在该语句块执行完毕后,局部变量就消失了。declare语句专门用于定义局部变量,可以使用default来说明默认值。set语句是设置不同类型的变量,包括会话变量和全局变量。 局部变量定义语法形式

 declare var_name [, var_name]... data_type [ DEFAULT value ];

例如在begin/end语句块中添加如下一段语句,接受函数传进来的a/b变量然后相加,通过set语句赋值给c变量。

set语句语法形式set var_name=expr [, var_name=expr]...; set语句既可以用于局部变量的赋值,也可以用于用户变量的申明并赋值。

 declare c int default 0;
 set c=a+b;
 select c as C;

或者用select …. into…形式赋值

 select into 语句句式:select col_name[,...] into var_name[,...] table_expr [where...];

例:

 declare v_employee_name varchar(100);
 declare v_employee_salary decimal(8,4);
 ​
 select employee_name, employee_salary
 into v_employee_name, v_employee_salary
 from employees
 where employee_id=1;

二、用户变量,在客户端链接到数据库实例整个过程中用户变量都是有效的。

MySQL中用户变量不用事前申明,在用的时候直接用“@变量名”使用就可以了。

第一种用法:set @num=1; 或set @num:=1; //这里要使用set语句创建并初始化变量,直接使用@num变量

第二种用法:select @num:=1; 或 select @num:=字段名 from 表名 where ……

select语句一般用来输出用户变量,比如select @变量名,用于输出数据源不是表格的数据。

注意上面两种赋值符号,使用set时可以用“=”或“:=”,但是使用select时必须用“:=赋值”

用户变量与数据库连接有关,在连接中声明的变量,在存储过程中创建了用户变量后一直到数据库实例接断开的时候,变量就会消失。

在此连接中声明的变量无法在另一连接中使用。

用户变量的变量名的形式为@varname的形式。

名字必须以@开头。

声明变量的时候需要使用set语句,比如下面的语句声明了一个名为@a的变量。

set @a = 1;

声明一个名为@a的变量,并将它赋值为1,MySQL里面的变量是不严格限制数据类型的,它的数据类型根据你赋给它的值而随时变化 。(SQL SERVER中使用declare语句声明变量,且严格限制数据类型。)

我们还可以使用select语句为变量赋值 。

比如:

set @name = '';
select @name:=password from user limit 0,1;#从数据表中获取一条记录password字段的值给@name变量。在执行后输出到查询结果集上面。

(注意等于号前面有一个冒号,后面的limit 0,1是用来限制返回结果的,表示可以是0或1个。相当于SQL SERVER里面的top 1)

如果直接写:select @name:=password from user;

如果这个查询返回多个值的话,那@name变量的值就是最后一条记录的password字段的值 。

用户变量可以作用于当前整个连接,但当当前连接断开后,其所定义的用户变量都会消失。

用户变量使用如下(我们无须使用declare关键字对用户变量进行定义,可以直接这样使用)定义,变量名必须以@开始:

#定义
select @变量名  或者 select @变量名:= 字段名 from 表名 where 过滤语句;
set @变量名;
#赋值 @num为变量名,value为值
set @num=value;或select @num:=value;

对用户变量赋值有两种方式,一种是直接用”=”号,另一种是用”:=”号。其区别在于使用set命令对用户变量进行赋值时,两种方式都可以使用;当使用select语句对用户变量进行赋值时,只能使用”:=”方式,因为在select语句中,”=”号declare语句专门用于定义局部变量。set语句是设置不同类型的变量,包括会话变量和全局变量。

begin
#Routine body goes here...
#select c as c;
declare c int default 0;
set @var1=143;  #定义一个用户变量,并初始化为143
set @var2=34;
set c=a+b;
set @d=c;
select @sum:=(@var1+@var2) as sum, @dif:=(@var1-@var2) as dif, @d as C;#使用用户变量。@var1表示变量名

set c=100;
select c as CA;
end

#在查询中执行下面语句段
call `order`(12,13);  #执行上面定义的存储过程
select @var1;  #看定义的用户变量在存储过程执行完后,是否还可以输出,结果是可以输出用户变量@var1,@var2两个变量的。
select @var2;

在执行完order存储过程后,在存储过程中新建的var1,var2用户变量还是可以用select语句输出的,但是存储过程里面定义的局部变量c不能识别。

系统变量:

系统变量又分为全局变量与会话变量。

全局变量在MySQL启动的时候由服务器自动将它们初始化为默认值,这些默认值可以通过更改my.ini这个文件来更改。

会话变量在每次建立一个新的连接的时候,由MySQL来初始化。MySQL会将当前所有全局变量的值复制一份。来做为会话变量。

(也就是说,如果在建立会话以后,没有手动更改过会话变量与全局变量的值,那所有这些变量的值都是一样的。)

全局变量与会话变量的区别就在于,对全局变量的修改会影响到整个服务器,但是对会话变量的修改,只会影响到当前的会话(也就是当前的数据库连接)。

我们可以利用

show session variables;

语句将所有的会话变量输出(可以简写为show variables,没有指定是输出全局变量还是会话变量的话,默认就输出会话变量。)如果想输出所有全局变量:

show global variables

有些系统变量的值是可以利用语句来动态进行更改的,但是有些系统变量的值却是只读的。

对于那些可以更改的系统变量,我们可以利用set语句进行更改。

系统变量在变量名前面有两个@;

如果想要更改会话变量的值,利用语句:

set session varname = value;
或者
set @@session.varname = value;

比如:

mysql> set session sort_buffer_size = 40000;
Query OK, 0 rows affected(0.00 sec)
用select @@sort_buffer_size;输出看更改后的值是什么。
如果想要更改全局变量的值,将session改成global:
set global sort_buffer_size = 40000;
set @@global.sort_buffer_size = 40000;

不过要想更改全局变量的值,需要拥有super权限 。

(注意,root只是一个内置的账号,而不是一种权限 ,这个账号拥有了MySQL数据库里的所有权限。任何账号只要它拥有了名为super的这个权限,就可以更改全局变量的值,正如任何用户只要拥有file权限就可以调用load_file或者into outfile,into dumpfile,load data infile一样。)

利用select语句我们可以查询单个会话变量或者全局变量的值:

select @@session.sort_buffer_size
select @@global.sort_buffer_size
select @@global.tmpdir

凡是上面提到的session,都可以用local这个关键字来代替。

比如:  

select @@local.sort_buffer_size
local是session的近义词。

无论是在设置系统变量还是查询系统变量值的时候,只要没有指定到底是全局变量还是会话变量。都当做会话变量来处理。

比如:

set @@sort_buffer_size = 50000; 
select @@sort_buffer_size; 

上面都没有指定是blobal还是session,所以全部当做session处理。

三、会话变量

服务器为每个连接的客户端维护一系列会话变量。在客户端连接数据库实例时,使用相应全局变量的当前值对客户端的会话变量进行初始化。设置会话变量不需要特殊权限,但客户端只能更改自己的会话变量,而不能更改其它客户端的会话变量。会话变量的作用域与用户变量一样,仅限于当前连接。当当前连接断开后,其设置的所有会话变量均失效。

设置会话变量有如下三种方式更改会话变量的值:

set session var_name = value;
set @@session.var_name = value;
set var_name = value;  #缺省session关键字默认认为是session

查看所有的会话变量
show session variables;
查看一个会话变量也有如下三种方式:
select @@var_name;
select @@session.var_name;
show session variables like "%var%";

凡是上面提到的session,都可以用local这个关键字来代替。

比如:

select @@local.sort_buffer_size 
local是session的近义词。

四、全局变量

全局变量影响服务器整体操作。当服务器启动时,它将所有全局变量初始化为默认值。这些默认值可以在选项文件中或在命令行中指定的选项进行更改。要想更改全局变量,必须具有super权限。全局变量作用于server的整个生命周期,但是不能跨重启。即重启后所有设置的全局变量均失效。要想让全局变量重启后继续生效,需要更改相应的配置文件。

要设置一个全局变量,有如下两种方式:

set global var_name = value; //注意:此处的global不能省略。根据手册,set命令设置变量时若不指定GLOBAL、SESSION或者LOCAL,默认使用SESSION
set @@global.var_name = value; //同上

查看所有的全局变量

show global variables; 

要想查看一个全局变量,有如下两种方式:

select @@global.var_name;
show global variables like “%var%”;

局部变量:

用户变量:

存储过程中的流程控制

分支语句

  • if-then-else

    if a=1 then
    	-- sql
    else 
    	-- sql
    end if;
    -------------------------------
    if a=1 then
    	-- sql
    elseif a=2 then
    	-- sql
    end if;

    例:

    -- 创建一个存储过程
    -- 如果参数输入的是1,则添加一条课程信息
    -- 如果参数输入的是2,则添加一条学生信息
    DROP PROCEDURE IF EXISTS stu_fun;
    CREATE PROCEDURE stu_fun(IN chioce INT)
    begin
    
    	IF chioce = 1 THEN
    		INSERT INTO course VALUES(11, "计算机网络", 2, 5, "郑瑞娟");
    	ELSEIF chioce = 2 THEN
    		INSERT INTO student VALUES(201215129, "杰哥", '男', 17, "MS", now());
    	END IF;
    
    end;
    
    call stu_fun(2);

  • case

CASE chioce
	WHEN 1 THEN
		-- sql
	WHEN 2 THEN
		-- sql
	ELSE
		-- sql
END CASE;

实例:

DROP PROCEDURE IF EXISTS stu_fun1;
CREATE PROCEDURE stu_fun1(IN chioce INT)
begin

	CASE chioce
	WHEN 1 THEN
		INSERT INTO course VALUES(12, "软件工程", 11, 5, "明");
	WHEN 2 THEN
		INSERT INTO student VALUES(201215128, "华强", '男', 30, "买瓜", now());
	ELSE
		SELECT chioce;
	END CASE;

end;

call stu_fun1(3);

<2>循环语句

  • while

    -- 创建一个存储过程,添加参数指定的学生信息
    DROP PROCEDURE IF EXISTS stu_fun2;
    CREATE PROCEDURE stu_fun2(IN num int)
    begin
    
    	DECLARE i INT DEFAULT 0;
    	while i <= num DO
    		INSERT INTO student(Sno) VALUES(CONCAT("20121513", i));
    		SET i = i + 1;
    	END WHILE;
    
    end;
    
    CALL stu_fun2(5);
  • repeat

    -- 创建一个存储过程,添加参数指定的学生信息
    DROP PROCEDURE IF EXISTS stu_fun3;
    CREATE PROCEDURE stu_fun3(IN num int)
    begin
    	DECLARE i INT DEFAULT 0;
    	repeat 
    		INSERT INTO student(Sno) VALUES(CONCAT("20121514", i));
    		Set i = i + 1;
    	UNTIL i > num end repeat;
    
    end;
    
    CALL stu_fun3(5);
  • loop

    -- 创建一个存储过程,添加参数指定的学生信息
    DROP PROCEDURE IF EXISTS stu_fun4;
    CREATE PROCEDURE stu_fun4(IN num int)
    begin
    
    	DECLARE i INT DEFAULT 0;
    	myloop:loop
    		DELETE FROM student WHERE Sno = CONCAT('20121513', i);
    		Set i = i + 1;
    		if i > num then
    			leave myloop;
    		end if;
    	end loop;
    
    end;
    
    call stu_fun4(9);

存储过程的管理

1、查询存储过程

存储过程是属于某个数据库的,只能在当前数据库中调用该存储过程

查询存储过程状态show procedure status where db = <数据库名>

查询存储过程的创建细节show create procedure mystudent.stu_fun3;

2、修改存储过程

修改存储过程指的是修改存储过程的特征

3、删除存储过程

drop procedure ;
-- 一般会只用以下语句删除
drop procedure if exists 

借书实例

1、创建学生信息表并添加信息

create table student(
    stu_id int primary key,
    stu_name varchar(20) unique not null,
    stu_gender char(2) not null,
    stu_age int not null
);

insert into student values (1001, '康康', '男', 12),
                           (1002, 'Maria', '女', 23),
                           (1003, 'Jane', '男', 22),
                           (1004, 'Jack', '男',21);

2、创建图书表并添加信息

create table book
(
    b_id     int primary key auto_increment,
    b_name   varchar(30) unique not null,
    b_author varchar(20)        not null,
    b_num    int                not null,
    b_price  decimal(10, 2)     not null,
    b_desc   varchar(200)
)character set utf8;

insert into book values (1, 'java高级编程', '小明明', 10, 23.3, 'java yyds'),
                        (2, 'java Web', '雄安王子', 2, 45.6, 'chao dnsjdk'),
                        (3, 'jquery', 'banana', 1, 45.6, 'good'),
                        (4, '计算机网络', 'snack', 2, 45.6, 'best');

3、创建借书记录表

create table record(
    b_id int not null,
    s_id int not  null,
    r_num int not null,
    is_return int not null,     -- 0 表示未归还  1 表示已归还
    r_date date not null,
    constraint FK_RECORD_STUDENT foreign key (s_id) references student(stu_id),
    constraint FK_RECORD_BOOK foreign key (b_id) references book(b_id)
);

4、创建借书存储过程

-- 实现借书业务
-- 参数1:a 输入参数 学号
-- 参数2:b 输入参数 图书编号
-- 参数3:c 输入参数 借书数量
-- 参数4:state 输出参数 结束的状态 1-借书成功 2- 学号不存在 3-图书不存在 4-库存不足
drop procedure if exists borrow;
create procedure borrow(IN a int, IN b int, IN c int, OUT state int)
begin

    declare s_count int default 0;
    declare b_count int default 0;
    declare num int default 0;
    select count(*) into s_count from student where stu_id = a;
    select count(*) into b_count from book where b_id = b;
    select b_num into num from book where b_id = b;

    if s_count > 0 then
        if b_count > 0 then
            if c <= num then
                insert into record values (b, a, c, 0, now());
                update book set b_num = b_num - c where b_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 @m = 0;
call borrow(1001, 1, 10, @m);

常见问题

在mysql8中定义函数时回报错

问题简介

[Err] 1418 - This function has none of DETERMINISTIC, NO SQL, or READS SQL DATA in its declaration and binary logging is enabled (you might want to use the less safe log_bin_trust_function_creators variable)

[Err] 1418-此函数中没有声明DETERMINISTIC,NO SQL或READS SQL DATA,只用声明才会启用二进制日志记录(您可能想使用不太安全的log_bin_trust_function_creators变量

解决方法:

  1. 第一种方法,需要声明DETERMINISTICNO SQLREADS SQL DATA的一种

  2. 第二种方法,信任子程序的创建者,设置变量log_bin_trust_function_creators的值设为1

select @@log_bin_trust_function_creators;

set global log_bin_trust_function_creators = 1;

使用样例

  1. 函数声明NO SQL

    -- mysql中创建一个自定义函数
    delimiter $$
    create function fun_addnum()
    -- 注意是returns而不是return
    returns int
    NO SQL
    begin 
    	set @i=0;
    	add_num:LOOP
            set @i=@i+1;
            -- leave用于跳出循环
            if @i=10 then leave add_num;
                -- iterate跳过后面语句进入下一循环
                elseif mod(@i,2)=0 then iterate add_num;  
            end if;
    	-- 结束循环
    	end loop add_num;
    	return @i;
    end $$
    delimiter ;
    
    select fun_addnum();

  2. 函数声明DETERMINISTIC,表示该函数在每次为其参数调用相同值时都返回相同的结果值

    delimiter $$
    create function fun_hello(x int)
    returns int
    DETERMINISTIC
    comment '这是注释' 
    begin 
    declare i int default 1; 
    declare j int default 10; 
    	case x
    		when i then set x=i; 
    		when j then set x=j;
    		else set x=i+j;
    	end CASE;
    return x;
    end $$
    delimiter ;
    
    select fun_hello(2);
  3. 设置变量log_bin_trust_function_creators的方式

    -- 设置为1,此时才可以使用 CONTAINS SQL,MODIFIES SQL DATA
    set GLOBAL log_bin_trust_function_creators=1;
    
    delimiter $$
    create function fun_temp()
    returns int
     -- READS SQL DATA
     MODIFIES SQL DATA
    begin 
    declare cnt int default 0;
    repeat 
    	set cnt=cnt+1;
    	until cnt=10 end repeat;
    return cnt;
    end $$
    delimiter ;

你可能感兴趣的:(mysql,数据库开发)