存储例程

2019-11-11

存储的例程是一组可以存储在服务器中的SQL语句。完成此操作后,客户无需继续重新发出各个语句,而是可以引用存储的例程。执行存储过程可以获取存储过程的结果集
存储的例程在某些情况下特别有用:

  • 当多个客户端应用程序以不同的语言编写或在不同的平台上工作时,但需要执行相同的数据库操作。
  • 当安全至上时。例如,银行将存储过程和函数用于所有常见操作。这提供了一致且安全的环境,例程可以确保正确记录每个操作。在这种设置中,应用程序和用户将无法直接访问数据库表,而只能执行特定的存储例程。

存储的例程可以提高性能,因为在服务器和客户端之间需要发送的信息较少。折衷方案是,这确实会增加数据库服务器的负载,因为更多的工作在服务器端完成,而较少的工作在客户端(应用程序)端完成。如果仅一台或几台数据库服务器为许多客户端计算机(例如Web服务器)提供服务,则应考虑这一点。
存储的例程还能够在数据库服务器中具有函数库。
存储的例程可以是过程,也可以是函数
当调用例程时,将执行隐式use db_name(当例程终止时将执行撤消)。不允许在存储例程中使用语句。
可以使用数据库名称限定例程名称。这可以用来引用不在当前数据库中的例程。例如,要调用与test数据库关联的存储过程p或函数f,可以调用test.p()或test.f()。
当一个数据库被删除时,所有与它相关的存储例程也被删除。
存储的函数不能递归
允许在存储过程中进行递归,但默认情况下禁用。要启用递归,请将max_sp_recursion_depth系统变量设置为大于零的值。存储过程递归增加了对线程堆栈空间的需求。如果增加max_sp_recursion_depth的值,则可能有必要通过增加服务器启动时thread_stack的值来增加线程堆栈的大小 。

权限

创建存储例程需要CREATE ROUTINE权限。
修改或删除存储例程需要ALTER ROUTINE特权。如果有必要,这个特权会自动授予例程的创建者,并在例程被删除时从创建者处删除。
执行存储例程需要EXECUTE特权。但是,如果有必要,这个特权会自动授予例程的创建者(并在例程被删除时从创建者处删除)。此外,例程的默认SQL安全性特征是DEFINER,它允许访问与例程关联的数据库的用户执行例程。
如果automatic_sp_privileges系统变量为0,则不会自动向例程创建者授予EXECUTE和ALTER例程特权,也不会从例程创建者中删除这些特权。
例程的创建者是用于为其执行CREATE语句的帐户。这可能与例程中定义为定义器的帐户不同。

存储过程

//存储过程
CREATE
    [DEFINER = user]
    PROCEDURE sp_name ([proc_parameter[,...]])
    [characteristic ...] routine_body

proc_parameter:
    [ IN | OUT | INOUT ] param_name type

type:
    Any valid MySQL data type

characteristic: {
    COMMENT 'string'
  | LANGUAGE SQL
  | [NOT] DETERMINISTIC
  | { CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA }
  | SQL SECURITY { DEFINER | INVOKER }
}
//存储函数
CREATE
    [DEFINER = user]
    FUNCTION sp_name ([func_parameter[,...]])
    RETURNS type
    [characteristic ...] routine_body

func_parameter:
    param_name type

type:
    Any valid MySQL data type

routine_body:
    Valid SQL routine statement

括号内的参数列表必须始终存在。如果没有参数,()则应使用的空参数列表 。参数名称不区分大小写。

每个参数默认情况下为IN参数。要为参数另外指定,请使用关键字OUT或INOUT在参数名称之前

一个IN参数传递给存储过程,该过程可能会修改该值,但是该过程返回时,调用者看不到该修改。一个OUT参数是返回给调用者的值,它的初始值是NULL,存储过程返回时,调用者可以看到其值 。一个 INOUT参数是由调用者者初始化,可以由程序进行修改并返回给调用者

对于每一个OUT或INOUT 参数,CALL 调用程序时传递一个用户定义的变量,这样就可以得到存储过程返回的值。如果要从另一个存储过程或函数中调用过程,则还可以将例程参数或本地例程变量作为OUT或INOUT参数传递。

MySQL允许存储过程包含DDL语句,例如 CREATE和DROP。MySQL还允许存储过程(但不允许存储函数)包含SQL事务语句,例如 COMMIT。存储过程可能不包含执行显式或隐式提交或回滚的语句,返回结果集的语句可以在存储过程中使用,但不能在存储函数中使用。存储过程中的USE语句不允许使用

定义存储过程:

delimiter //  #将sql终止符更改为//
create procedure pl()  # 创建一个名为pl的存储过程
BEGIN    #开始创建存储过程
   sql 语句
END //  #结束创建存储过程
delimiter ;

执行存储过程: call pl()  

存储过程修改比较麻烦,建议直接删除原来的存储过程,然后在创建新的存储过程

存储过程需要给参数定义类型,而且当传值时必须对应指定的类型

存储过程的参数: 参数只能设定基础类型
1、in    表示将参数传给存储过程内部使用
2、out  表示将参数返回(类似 return),传入的参数的值不会传入到存储过程内部,call 调用时传入的参数必须为 @参数名(mysql定义变量方法),这样的存储过程执行后 select @参数名 即可获取值   out的参数可以不传值
3、inout  表示将参数传给存储过程内部使用,并返回这个参数 (可以传值 或者变量)

存储过程中赋值:

declare 用于定义变量
set  用于设置变量的值例:

delimiter //
create procedure p1(
    in arg1 int,
    out  arg2 varchar(50),
    inout arg3 int
)
begin
    declare v int;
    declare g int default 9;
    set v = 1;
    arg2 = 'eric';
    arg3 = arg3 + 666;
    update table set name = 'what' where id = 1;
    select * from table;

end //
delimiter ;

@i2 = 123;
call p1(1,@i1,@i2)

select @i1,@i2   --》输出为:eric,789

//存储过程可以获取多种数据:数据值和结果集 (数据一般用来判断执行是否成功,比如0代表失败,1代表成功) 存储过程只能有一个结果集(也就意味着只能有一个select语句)

存储过程动态执行sql语句:可以有效的防止sql注入

delimiter  \\
create procedure p4 (
    in nid int
)
begin 
    set nid = 11;
    set @nid = nid;

    PREPARE prod FROM 'select * from student where sid > ?';   // ?时mysql中的占位符  创建一个变量 prod 代表后面的sql语句字符串
    EXECUTE prod USING @nid;  //字符串格式化 ,将nid拼接到sql语句中的占位符处, msyql要求拼接的变量前面必须带@
    DEALLOCATE prepare prod; //执行sql语句
end  \\
delimiter ;

查询存储过程

方法1:select name from mysql.proc where db='数据库名';

方法2:select  routine_name from information_schema.routines where routine_schema='数据库名';

方法3:show procedure status where db='数据库名';

查看详细信息:show create procedure 数据库.存储过程名

删除存储过程

DROP {PROCEDURE | FUNCTION} [IF EXISTS] sp_name

调用存储过程

CALL sp_name([parameter[,...]])

CALL可以使用OUT或INOUT参数将值返回给调用者。当过程返回时,客户端程序还可以获得在例程中执行的最终语句所影响的行数;
要使用OUT或INOUT参数从过程中获取值,需要传递自定义变量作为参数,然后在过程返回后检查变量的值。
如果要从另一个存储过程或函数中调用过程,则还可以将例程参数或本地例程变量作为IN或INOUT、INOUT参数传递。

查看例程定义

SHOW CREATE PROCEDURE
SHOW CREATE FUNCTION

查看例程内部实现的表示

SHOW PROCEDURE CODE proc_name
SHOW FUNCTION CODE func_name

返回存储过程的特征,例如数据库,名称,类型,创建者,创建和修改日期以及字符集信息

SHOW PROCEDURE STATUS [LIKE 'pattern' | WHERE expr]
SHOW FUNCTION STATUS [LIKE 'pattern' | WHERE expr]

存储例程的元数据

SELECT * from INFORMATION_SCHEMA.ROUTINES

示例:

清空数据库中所有的表的数据,并将表的自增值改为1

delimiter \\
CREATE PROCEDURE clear_all ( IN database_name VARCHAR ( 50 ) ) 
BEGIN
	DECLARE  done INT DEFAULT 0;
	DECLARE  table_name1 VARCHAR ( 100 );
			
	-- 定义游标,并将结果集赋值到游标中
	DECLARE  all_table CURSOR FOR SELECT table_name  FROM information_schema.TABLES  WHERE table_schema = database_name  AND table_type = 'base table';
	-- 声明当游标遍历完后将标志变量置成某个值
	DECLARE CONTINUE HANDLER FOR NOT found  SET done = 1;
			
	-- 打开游标
	OPEN all_table;
			
		-- 将游标中的值赋值给变量,注意:变量名不要和返回的列名同名,变量顺序要和sql结果列的顺序一致
		FETCH all_table INTO table_name1;
			
		WHILE  done <> 1 DO
							
			-- 预定义sql,因为是动态的选择表名,所以表名并不能用 ? 来当占位符,只能采用字符串拼接的方法。
			SET @sql1 = concat( 'truncate ', table_name1, ';' );
			PREPARE truncate_sql  FROM @sql1;
			EXECUTE truncate_sql;
			DEALLOCATE PREPARE truncate_sql;
								
			-- 将游标中的值再赋值给变量,供下次循环使用
			FETCH all_table INTO table_name1;
								
		END WHILE;
			
	CLOSE all_table;
	
END \\ 
delimiter;

删除数据库中所有表的数据,但是每个数据表保留10条数据

delimiter \\
CREATE PROCEDURE clear_all ( IN database_name VARCHAR ( 50 ) ) 
BEGIN
	DECLARE  done INT DEFAULT 0;
	DECLARE  table_name1 VARCHAR ( 100 );
			

	DECLARE  all_table CURSOR FOR SELECT table_name  FROM information_schema.TABLES  WHERE table_schema = database_name  AND table_type = 'base table';

	DECLARE CONTINUE HANDLER FOR NOT found  SET done = 1;
			
	-- 打开游标
	OPEN all_table;
			
		-- 将游标中的值赋值给变量,注意:变量名不要和返回的列名同名,变量顺序要和sql结果列的顺序一致
		FETCH all_table INTO table_name1;
			
		WHILE  done <> 1 DO
							
			SET @sql1 = concat( 'select count(*) into  @limit1 from ', table_name1);
			
			PREPARE truncate_sql  FROM @sql1;
			EXECUTE truncate_sql;
			DEALLOCATE PREPARE truncate_sql;

			SET @limit1 = @limit1 - 10;
			
			IF @limit1 >= 1 THEN
				-- 预定义sql,因为是动态的选择表名,所以表名并不能用 ? 来当占位符,只能采用字符串拼接的方法。
						SET @sql1 = concat( 'delete from ', table_name1, '  limit ', @limit1 );
						PREPARE truncate_sql  FROM @sql1;
						EXECUTE truncate_sql;
						DEALLOCATE PREPARE truncate_sql;
			END IF;
			-- 将游标中的值再赋值给变量,供下次循环使用
			FETCH all_table INTO table_name1;
								
		END WHILE;
			
	CLOSE all_table;
	
END \\ 
delimiter;

 

{/if}