MySQL原理(八):触发器和存储过程

前言

上一篇介绍了 MySQL 的内存管理和磁盘管理,这一篇将介绍存储过程和触发器相关的内容。

存储过程

存储过程是一组为了完成特定功能的 SQL 语句集合,使用存储过程的目的在于:将常用且复杂的 SQL 语句预先写好,然后用一个指定名称存储起来,这个过程经过编译解析、执行优化后存储在数据库中,当以后需要使用这个过程时,只需调用根据名称调用即可。

优点:

  • 复用性:可以在程序中被反复调用;
  • 灵活性:可以定义变量、使用流程控制等,相较于普通的 SQL 语句更灵活;
  • 降低网络负载:相较于将整条 SQL 语句通过网络传输再运行的方式,可以降低网络负载;
  • 高性能:存储过程执行后,会将语句编译成机器码驻留在线程缓冲区,后续调用无需再次编译执行;
  • 安全性:对于不同的存储过程,可以根据权限设置执行的用户;

缺点:

  • 内存占用高:存储过程执行后,会将语句编译成机器码驻留在线程缓冲区,如果大量使用,会造成内存占用率高;
  • 维护性差:存储过程要求开发人员掌握相关的语法,且不支持 Debug 调试;

存储过程难以维护,同时扩展性和移植性都很差,所以一般都不会使用。但是在以下场景中可以使用存储过程:

  • 插入测试数据;
  • 对数据做批处理操作;

定义

DELIMITER $

-- 创建的语法:指定名称、入参、出参
CREATE 
    PROCEDURE 存储过程名称(返回类型 参数名1 参数类型1, ....)
    [ ...约束条件... ]
-- 表示开始编写存储过程体
BEGIN
    -- 具体组成存储过程的SQL语句....
-- 表示到这里为止,存储过程结束
END $

DELIMITER ;

DELIMITER $ 表示指定结束标识。在 MySQL 中默认是以分号 ; 作为一条语句的结束标识,因此当存储过程的过程体中,如果包含了 SQL 语句,且以分号结束时, 会认为存储过程的定义也结束了,过程体就会和结束符冲突,所以一般要重新定义结束符,例如DELIMITER $,表示以 $ 作为结束标识,只有当识别到 $ 符时,才会认为结束了。所以在结束后,需要再次把结束符改回分号,即 DELIMITER ;

类型

存储过程支持四种类型,主要依赖于 IN、OUT、INOUT 三个关键字来区分:

  • 定义存储过程时,没有入参也没有出参,代表无参无返回类型。
  • 定义存储过程时,仅定义了带有 IN 类型的参数,表示有参无返回类型。
  • 定义存储过程时,仅定义了带有 OUT 类型的参数,表示无参有返回类型。
  • 定义存储过程时,同时定义了带有 IN、OUT 类型的参数,或定义了带有 INOUT 类型的参数,表示有参有返回类型。

约束条件

  • LANGUAGE SQL:说明存储过程中的过程体是否由 SQL 语句组成;
  • [NOT] DETERMINISTIC:说明存储过程的返回值是否为固定的,没有 NOT 表示为固定的,默认为非固定的。
  • { CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA }:说明过程体使用 SQL 语句的限制:
    • NO SQL:表示当前存储过程中不包含任何 SQL 语句;
    • CONTAINS SQL:表示当前存储过程包含 SQL,但不包含读写数据的 SQL 语句;
    • READS SQL DATA:表示当前存储过程中包含读数据的 SQL 语句;
    • MODIFIES SQL DATA:表示当前存储过程中包含写数据的 SQL 语句;
  • SQL SECURITY { DEFINER | INVOKER }:说明哪些用户可以调用当前创建的存储过程:
    • DEFINER:表示只有定义当前存储过程的用户才能调用。
    • INVOKER:表示任何具备访问权限的用户都能调用。
  • COMMENT ‘…’:注释信息,可以用来描述当前创建的存储过程。

示例:

DELIMITER $

CREATE 
    -- 在定义存储过程时,用 OUT 声明了一个返回值
    PROCEDURE get_user_id(INOUT parameters varchar(255))
BEGIN
    select `user_id` into parameters from `zz_users` where `user_name` = parameters;
END $

DELIMITER ;

变量

系统变量

系统变量是系统定义的,在 MySQL 中全局生效,也可以通过 session 关键字,表示只对当前连接生效。

对于系统变量,想要查看或修改,使用两个 @@ 符号即可,例如:

-- 查看某个系统变量
select @@xxx;
-- 修改某个系统变量
set @@xxx = "xxx";

用户变量

用户自定义的变量,想要查看或修改,使用一个 @ 符号即可,例如:

set @变量名称 = 变量值;
select @变量名称;

用户变量的赋值,中间的 = 也可改为 :=,其作用也是相同的。除此之外,用户变量的定义还可以和 SQL 组合,如下:

-- 将用户表的总行数赋值给 row_count 变量
select @row_count := count(*) from `zz_users`;
-- 将 user_id 的平均值赋给 avg_user_id 变量
select avg(user_id) into @avg_user_id from `zz_users`;

局部变量

局部变量的有效范围只是当前存储过程,定义方式如下:

DECLARE 变量名称 数据类型 default 默认值;
DECLARE message varchar(255) default "not message";
-- 赋值方式一
SET message = 变量值;
SET message := 变量值;
-- 赋值方式二
select 字段名或函数 into message from 表名;

对于局部变量的定义,必须要写在 BEGIN、END 之间,否则会提示语法错误。

条件判断IF

IF 条件判断 THEN
    -- 分支操作.....
ELSEIF 条件判断 THWN
    -- 分支操作.....
ELSE
    -- 分支操作.....
END IF

分支判断CASE

-- 第一种语法
CASE 变量
    WHEN1 THEN
        -- 分支操作1....
    WHEN2 THEN
        -- 分支操作2....
    .....
    ELSE
        -- 分支操作n....
END CASE;

LOOP循环

在存储过程中可以给每个循环取一个名字,如果想要跳出一个循环,需要结合 LEAVE 这个关键字,否则会令循环成为一个死循环。

循环名称:LOOP
    -- 循环体....
END LOOP 循环名称;

WHILE循环

【循环名称】:WHILE 循环条件 DO
    -- 循环体....
END WHILE 【循环名称】;

REPEAT循环

【循环名称】:REPEAT
    -- 循环体....
UNTIL 结束循环的条件判断
END REPEAT 【循环名称】;

跳转

LEAVE、ITERATE 两个跳转的关键字,就相当于 break 和 continue。

-- 跳出某个循环
LEAVE 循环名称;
-- 跳过某次循环
ITERATE 循环名称;

游标

游标可以对一个结果集中的数据按条处理,也就意味着原本查询出的数据是一个整体性质的集合,而使用游标可以对该集合中的数据逐条处理,在使用游标时一般都会遵循下述四步:

-- ①声明(创建)游标
DECLARE 游标名称 CURSOR FOR select ...;
-- ②打开游标
OPEN 游标名称;
-- ③使用游标
FETCH 游标名称 INTO 变量名称;
-- ④关闭游标
CLOSE 游标名称;

示例:

DELIMITER $

CREATE 
    PROCEDURE id_odd_number_sum(IN N int(8), OUT sum int(8))
BEGIN
		-- 声明局部变量:
		-- 		uid:用于记录每一个user_id
		--		odd_id_count:记录奇数ID的个数
		--		odd_id_sum:记录奇数ID的和
		DECLARE uid int(8) DEFAULT 0;
		DECLARE odd_id_count int(8) DEFAULT 0;
		DECLARE odd_id_sum int(8) DEFAULT 0;
		
		-- 声明一个游标:存储倒序的user_id结果集
		DECLARE uid_cursor CURSOR FOR select user_id from zz_users order by user_id desc;
    
    -- 打开游标
	OPEN uid_cursor;
	
	-- 使用游标
	REPEAT
	-- 将游标中的每一条user_id值,赋给user_id变量
			FETCH uid_cursor INTO uid;
			
			-- 如果当前user_id是奇数,则将ID值累加到sum中
			IF uid % 2 != 0 THEN
				SET odd_id_count = odd_id_count + 1;
				SET odd_id_sum = odd_id_sum + uid;
			END IF;
	-- 根据传入的N来决定循环的次数
	UNTIL odd_id_count >= N END REPEAT;
	
	-- 将前N个奇数ID之和赋给外部变量:sum
	SET sum = odd_id_sum;
	-- 关闭游标
	CLOSE uid_cursor;
END $

DELIMITER ;

管理命令

在 MySQL 中也提供了一系列命令,来完成存储过程的查看、修改和删除,如下:

  • SHOW PROCEDURE STATUS;:查看当前数据库中的所有存储过程。
  • SHOW PROCEDURE STATUS WHERE db = '库名' AND NAME = '过程名';:查看指定库中的某个存储过程。
  • SHOW CREATE PROCEDURE 存储过程名;:查看某个存储过程的源码。
  • ALTER PROCEDURE 存储过程名称 ....:修改某个存储过程的特性。
  • DROP PROCEDURE 存储过程名;:删除某个存储过程。

也可以通过以下命令来查看某张表的存储过程:

-- 查看某张表的所有存储过程
select * from 表名.Routines where routine_type = "PROCEDURE";

-- 查看某张表的某个存储过程
select * from 表名.Routines where routine_name = "过程名" AND routine_type = "PROCEDURE";

触发器

触发器本质上是一种特殊的存储过程,存储过程需要人为手动调用,而触发器则不需要,它可以在执行某项数据操作后自动触发

对于每一个触发器而言,总共有插入、修改、删除三种触发事件可选,同时也可以选择将触发器放在事件开始前或结束后执行。另外,创建触发器时必须要指定表名。创建语法如下:

CREATE TRIGGER 触发器名称
    {BEFORE | AFTER} {INSERT | UPDATE | DELETE} ON 表名
FOR EACH ROW
    -- 触发器的逻辑(代码块);

使用示例:

DELIMITER $

CREATE TRIGGER zz_users_insert_before
  BEFORE INSERT ON zz_users
FOR EACH ROW
	BEGIN
		insert into `register_log` values(NOW(),"北京市海淀区","IOS");
	END $
		
DELIMITER ;

NEW、OLD关键字

在触发器中,NEW 表示新数据,OLD 表示老数据,各类型的事件如下:

  • insert 插入事件:NEW 表示当前插入的这条行数据。
  • update 修改事件:NEW 表示修改后的新数据,OLD 表示修改前的老数据。
  • delete 删除事件:OLD 表示删除前的老数据。

使用示例:

DELIMITER $

CREATE TRIGGER zz_users_update_before
  BEFORE UPDATE ON zz_users
FOR EACH ROW
	BEGIN
		DECLARE new_name varchar(255);
	    DECLARE old_name varchar(255);
	    
		-- 可以通过 NEW 关键字拿到修改后的新数据(粉熊)
		SET new_name := NEW.user_name;
		
		-- 可以通过 OLD 关键字拿到修改前的老数据(棕熊)
		SET old_name := OLD.user_name;
	END $
		
DELIMITER ;

管理命令

触发器的管理命令如下:

  • SHOW TRIGGERS;:查看当前数据库中定义的所有触发器。
  • SHOW CREATE TRIGGER 触发器名称;:查看当前库中指定名称的触发器。
  • SELECT * FROM information_schema.TRIGGERS;:查看MySQL所有已定义的触发器。
  • DROP TRIGGER IF EXISTS 触发器名称;:删除某个指定的触发器。

最后

本文介绍了 MySQL 存储过程和触发器。

下一节将介绍 MySQL 的表分区和分库分表。

你可能感兴趣的:(MySQL,mysql,数据库,sql,存储过程,触发器)