【MySQL数据库设计与应用(六)】存储程序(存储过程,存储函数,触发器,事件)

文章目录

    • 1 存储程序介绍
      • 1.1 什么是存储程序
      • 1.2 存储例程
      • 1.3 触发器
      • 1.4 事件
    • 2 创建和调用存储过程
      • 2.1 创建和调用存储过程
      • 2.2 存储过程的参数模式
      • 2.3 存储过程返回结果集
      • 2.4 存储过程的安全上下文
    • 3 创建和调用存储函数
      • 3.1 创建和调用存储函数
      • 3.2 存储函数和存储过程的主要差异
      • 3.3 存储函数和存储过程的共性特征
    • 4 管理存储例程
      • 4.1 查看存储例程的状态和定义
      • 4.2 删除存储例程
      • 4.3 修改存储例程的定义
      • 4.4 修改存储例程的特性
      • 4.5 授权执行存储例程
    • 5 在存储程序中使用游标
    • 6 在存储程序中使用事务
    • 7 触发器
      • 7.1 创建触发器
      • 7.2 查看触发器的定义
      • 7.3 删除触发器
    • 8 用事件定时执行任务
      • 8.1 事件的概念和基本属性
      • 8.2 创建事件
      • 8.3 查看事件的定义
      • 8.4 修改事件的属性和定义
      • 8.5 删除事件

1 存储程序介绍

1.1 什么是存储程序

存储程序是预先在数据库服务器端存储 SQL 命令/语句,并且过后能够在数据库服务器端被执行的数据库对象。

  • 存储程序的主体
    存储程序定义的主体中除可以包括常规的 SQL 语句外,通常还使用变量声明,选择,循环,复合语句等
  • 使用存储程序
    利用 CALL 语句等方式使用存储程序

存储程序的分类

  • 存储例程:和一个数据库相关,可以按照名字调用
  • 触发器:和一个表相关,并在该表产生特定事件时被触发
  • 事件:和一个数据库相关,代表了由 MySQL 服务器的事件调度器在特定时刻调度执行的任务。

1.2 存储例程

存储例程是存储在服务器端的 SQL 语句的集合,能够用存储例程的名字重用相应的语句代码。

经常用于提高效率和安全性:
(1)减少在 MySQL 服务器和 MySQL 客户端的数据传输
(2)对存储例程的授权管理更易于结合应用系统安全性
(3)存储例程中也是一个很好的记录日志的地方

包含过程和函数:PROCEDURE 和 FUNCTION

  • 存储过程没有返回值,通过 CALL 语句调用
  • 存储函数通过 RETURN 语句返回一个值,类似于内置函数

存储过程也是能“返回”值甚至结果集的

  • 存储过程可以使用输出类型的参数来传递值
  • 存储过程允许使用 SELECT 语句,从而使得存储过程执行后能够向客户端返回结果集

1.3 触发器

触发器是数据库中的命名对象,与一个表关联,并且在该表上的 INSERT,UPDATE,DELETE 等更改操作前/后被触发。

定义触发器
(1)WHERE:在哪张表上
(2)WHEN:由什么操作触发
(3)WHAT:被触发时所要执行的 SQL 命令

触发器的典型应用

  • 实现自定义完整性约束,
    例如一位教师在同意学期最多只允许承担 3 门课程
  • 用于值得计算
    例如订单明细发生改变时,重新计算订单金额并更新相关表(例如订单,库存等)中得相关记录
  • 日志或副本记录
    课确保系统跟踪和审计“时变”数据。

1.4 事件

事件是指在 MySQL 事件调度器的调度下,在特定的时刻所执行的任务,因此也称调度事件

定义事件的要素

  • 事件的“时刻”属性:在某一时刻仅执行一次,或按照事件间隔周期性的执行多少次
  • 事件的“任务”属性:要执行的 SQL 语句

事件典型应用

  • 常用于执行无人值守的系统管理任务,例如:更新汇总报告

2 创建和调用存储过程

存储例程是存储在服务器端的 SQL 语句的集合,利用存储例程的名字可重用相应的语句代码。存储过程是一种存储例程。
【MySQL数据库设计与应用(六)】存储程序(存储过程,存储函数,触发器,事件)_第1张图片

2.1 创建和调用存储过程

  • 使用 CREATE PROCEDURE 语句创建存储过程

example:创建一个存储过程,用于备份表纪律到备份表中。

create procedure backup()
insert into t_bak select * from t;
  • 用 CALL 语句调用存储过程
mysql> call backup();
Query OK, 2 rows affected (0.10 sec)
  • 存储过程的处理需要多条 SQL 语句,使用 DELIMITER 命令定义语句定界符

example:备份表记录到备份表后,删除原表的记录

delimiter $$

create procedure backup2()
begin
	insert into t_bak select * from t;
	delete from t;
end $$

delimiter ;

2.2 存储过程的参数模式

存储过程的参数类型可以是 MySQL 的有效数据类型。存储过程的参数有 IN,OUT 和 INOUT 三种模式。
【MySQL数据库设计与应用(六)】存储程序(存储过程,存储函数,触发器,事件)_第2张图片
存储过程的 IN 参数
例如,要求存储过程备份那些关键字段值小于给定值的记录。IN 可以省略书写。

delimiter $$

create procedure backup3(n int)
begin
	insert into t_bak select * from t where id <= n;
	delete from t where id <= n;
end $$

delimiter ;
mysql> call backup3(108);
Query OK, 1 row affected (0.10 sec)

存储过程的 OUT 参数
example:修改前述存储过程,使之传回本次备份的记录数。

delimiter $$

create procedure backup4(n int, out record_count int)
begin
	insert into t_bak select * from t where id <= n;
	select count(*) into record_count from t where id <= n;
	delete from t where id <= n;
end $$

delimiter ;
mysql> set @rec_count = 0;
Query OK, 0 rows affected (0.00 sec)

mysql> call backup4(108, @rec_count);
Query OK, 1 row affected (0.12 sec)

mysql> select @rec_count;
+------------+
| @rec_count |
+------------+
|          1 |
+------------+
1 row in set (0.00 sec)

2.3 存储过程返回结果集

存储过程中允许使用 SELECT 等语句返回结果集

delimiter $$

create procedure backup5(n int)
begin
	insert into t_bak select * from t where id <= n;
	select * from t where id <= n;
	delete from t where id <= n;
end $$

delimiter ;
mysql> call backup5(108);
+-----+--------------+
| id  | name         |
+-----+--------------+
| 101 | 机械工程学院 |
+-----+--------------+
1 row in set (0.08 sec)

Query OK, 1 row affected (0.10 sec)

2.4 存储过程的安全上下文

执行存储过程中的安全上下文
有权执行某个存储过程中的用户在执行存储过程时,存储过程内的 SQL 语句的执行,按定义者(默认)或调用者的权限进行检查。

谁是调用者,谁是定义者
调用者:执行 CALL 语句的用户
定义者:默认是执行 CREATE PROCEDURE 语句的用户,也可以用 DEFINER 子句指定另外的用户名

定义者和调用者安全上下文比较
试想:用户(调用者)执行某存储过程,且存储过程中读写某个表,存储过程的调用者没有这个表的读或写权限;存储过程的定义者拥有这个表的读或写权限。则定义者权限模式正常执行,调用者模式导致权限错误。

结论:定义者安全上下文有利于保证用户只能通过存储过程对数据进行有限的访问。

3 创建和调用存储函数

3.1 创建和调用存储函数

用 CREATE FUNCTION 语句创建函数

CREATE FUNCTION sp_name([func_parameter[,...]])
	RETURNS type		// 存储函数必须说明返回值类型
	routine_body		// 存储函数必须用 RETURN 返回值
	
	func_parameter.param_name type	// 参数均视为 IN 参数

调用函数的方式
不需要像存储过程那样使用 CALL 语句,直接使用存储函数名带参数的形式,存储函数的调用可以出现在很多地方。

example:

create function hello(name char(20))
	returns char(50)
return concat('Hello,', name, '!');
mysql> select hello('MySQL');
+----------------+
| hello('MySQL') |
+----------------+
| Hello,MySQL!   |
+----------------+
1 row in set (0.00 sec)

功能相同的存储过程和存储函数
例如:用存储函数的方式实现前面实现过的存储过程。

delimiter $$

create procedure backup4(n int, out record_count int)
begin
	insert into t_bak select * from t where id <= n;
	select count(*) into record_count from t where id <= n;
	delete from t where id <= n;
end $$

delimiter ;

将存储过程改写成存储程序如下:

delimiter $$

create function backupf(n int)
	returns int
begin
	declare record_count int;
	insert into t_bak select * from t where id <= n;
	select count(*) into record_count from t where id <= n;
	delete from t where id <= n;
	return record_count;
end $$

delimiter ;

3.2 存储函数和存储过程的主要差异

  • 向调用程序传回数据的方式不同
    存储过程借助 OUT 参数和返回结果集的方式
    存储函数使用 return 返回值的方式

  • 调用方式不同
    存储函数比存储过程的人调用更为灵活

  • 主体内允许的 SQL 不同
    存储过程中的主体中可以使用绝大多数 SQL
    存储函数受较多的限制,例如不允许包含常规 SELECT 语句

3.3 存储函数和存储过程的共性特征

存储例程特性的说明
characteristic:
| [NOT] DETERMINISTIC
| {NO SQL | CONTAINS SQL | READS SQL DATA | MODIFIES SQL DATA }
| SQL SECURITY { DEFINER | INVOKER }

存储例程的确定性
[NOT] DETERMINISTIC,确定性,即给定同样的输入参数是否总是产生相同的结果。

例如 MySQL 内置函数 NOW() 就是不确定的,不同的时间会得到不同的输出,而 hello() 则是确定的,对应确定的参数有确定的结果。

默认选项是 NOT DETERMINISTIC。该选项会对优化产生影响。例如,执行 DETERMINISTIC 时 MySQL 可能使用缓冲等优化手段。

存储例程特性的数据访问特性
NO SQL | CONTAINS SQL | READS SQL DATA | MODIFIES SQL DATA,说明存储例程的数据访问特性,默认选项 CONTAINS SQL。

NO SQL 指示存储例程不含括 SQL 语句;

CONTAINS SQL 指示该存储例程不包含读或写数据的 SQL 语句;

READS SQL DATA 指示存储例程包括读数据的语句(例如 SELECT)但不包括写数据的语句;

MODIFIES SQL DATA 指示存储例程包含写数据的语句(例如 INSERT)。

4 管理存储例程

4.1 查看存储例程的状态和定义

查看存储例程的状态

  • SHOW PROCEDURE STATUS [like…or…where]
  • SHOW FUNCTION STATUS [like…or…where]

example:

mysql> SHOW PROCEDURE STATUS LIKE 'backup'\G	# \G表示竖排显示
*************************** 1. row ***************************
                  Db: db
                Name: backup
                Type: PROCEDURE
             Definer: root@localhost
            Modified: 2020-05-06 16:26:19
             Created: 2020-05-06 16:26:19
       Security_type: DEFINER
             Comment:
character_set_client: gbk
collation_connection: gbk_chinese_ci
  Database Collation: utf8_general_ci
1 row in set (0.00 sec)

查看存储例程的定义

  • SHOW CREATE PROCEDURE proc_name
  • SHOW CREATE FUNCTION func_name
mysql> SHOW CREATE PROCEDURE backup\G
*************************** 1. row ***************************
           Procedure: backup
            sql_mode: STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION
    Create Procedure: CREATE DEFINER=`root`@`localhost` PROCEDURE `backup`()
insert into t_bak select * from t
character_set_client: gbk
collation_connection: gbk_chinese_ci
  Database Collation: utf8_general_ci
1 row in set (0.00 sec)

4.2 删除存储例程

mysql> DROP PROCEDURE backup;
Query OK, 0 rows affected (0.13 sec)

mysql> DROP PROCEDURE backup;
ERROR 1305 (42000): PROCEDURE db.backup does not exist

mysql> DROP PROCEDURE IF EXISTS backup;
Query OK, 0 rows affected, 1 warning (0.00 sec)

4.3 修改存储例程的定义

删除并创新创建存储程序,需要先 DROP 后 CREATE

mysql> drop procedure if exists backup;
Query OK, 0 rows affected, 1 warning (0.00 sec)

mysql> create procedure backup()
    -> insert into t_bak select * from t;
Query OK, 0 rows affected (0.00 sec)

4.4 修改存储例程的特性

  • ALTER PROCEDURE proc_name [ characteristic… ]
  • ALTER FUNCTION func_name [ characteristic… ]

characteristic:
COMMENT ‘string’
| LANGUAGE SQL
| { CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA }
| SQL SECURITY { DEFINER | INVOKER }

alter procedure backup
    -> comment '备份表t到t_bak'
    -> modifies sql  data;
Query OK, 0 rows affected (0.05 sec)

【MySQL数据库设计与应用(六)】存储程序(存储过程,存储函数,触发器,事件)_第3张图片

4.5 授权执行存储例程

用 GRANT 语句授权用户执行存储例程
用户执行存储例程需要存储例程对象上的 EXECUTE 权限

GRANT EXECUTE ON [{ PROCEDURE | FUNCTION}]
{ *.* | db_name.* | db_name.routine_name } TO user

example:

GRANT EXECUTE ON course.* TO 'user1';

GRANT EXECUTE ON PROCEDURE course.backup TO 'user2';

5 在存储程序中使用游标

存储程序中对结果集每行记录依次处理,需要使用游标(CURSOR)。

游标的作用

  • 在存储程序中编程访问 SELECT 所返回结果集
  • 方便逐行访问并对每行记录完成相应的处理

游标的使用

  • 先声明
    DECLARE CURSOR 和 DECLARE HANDLER
  • 后使用
    OPEN,使用游标必须先显式打开游标
    FETCH,提取当前行记录字段值
    CLOSE,最后关闭游标

example:计算学生平均绩点
把学生所选的每门课程的百分制成绩折算成绩点(例如按五级制 A/B/C/D/F 分别折算 4/3/2/1/0),将课程学分和课程绩点相乘,得到课程绩点,除以总的课程学生即平均绩点。

delimiter $$

create function gpa(stu_id char(12))
	returns decimal(3, 2)
begin
	declare score, credit, points, total_credit, total_points int default 0;
	declare done int default FALSE;
	declare cursor_choose_course cursor for
	select choose.score, course.credit from choose
	join course on choose.Course_id = course.Course_id
	where choose.Student_id = stu_id and choose.score IS NOT NULL;
declare continue handler for not found set done = TRUE;

open cursor_choose_course;
loop_cursor: loop
	fetch cursor_choose_course into score, credit;
	if done then leave loop_cursor;
	end if;
	set total_credit = total_credit + credit;
	if score >= 90 then set points = 4;
	elseif score >= 80 then set points = 3;
	elseif score >= 70 then set points = 2;
	elseif score >= 60 then set points = 1;
	else set points = 0;
	end if;
	set total_points = total_points + points * credit;
end loop;
close cursor_choose_course;
return if (total_credit > 0, total_points/total_credit, 0);
end $$

delimiter ;
mysql> select gpa('M20177001');
+------------------+
| gpa('M20177001') |
+------------------+
|             4.00 |
+------------------+
1 row in set (0.41 sec)

6 在存储程序中使用事务

MySQL 默认在每一条 SQL 语句执行后都自动提交(事务),也允许在存储过程中使用显式地事务控制。

事务控制原则

  • 根据需要手工启动事务
  • 根据处理情况(成功时)提交事务或(失败时)回滚事务

常用的事务控制语句

  • START TRANSACTION ——用于启动事务
  • COMMIT ——用于提交事务
  • ROLLBACK ——用于回滚事务

事务经典案例:银行账户转账

对事务的要求

  • 转账先向转入账户增加余额,再从转出账户扣减余额
  • 如遇转出账户不够扣减,则放弃所有更改(即回滚事务)
delimiter $$

create procedure transfer(account_from int, account_to int, amount int, OUT status int)
modifies sql data
begin
	declare account_from_balance int;
	declare exit handler for sqlexception begin rollback;
	set status = -1;
	end;
	
	start transaction;
	update bank_account set Balance = Balance + amount where Account_Id = account_to;
	select balance into account_from_balance from bank_account where Account_Id = account_from;
	if account_from_balance < amount then
		rollback;
		set status = -1;
	else
		update bank_account set Balance = Balance - amount where Account_Id = account_from;
		commit;
		set status = 0;
	end if;
end $$

delimiter ;

初始情况

+------------+---------+
| Account_Id | Balance |
+------------+---------+
|          1 |     500 |
|          2 |    1000 |
+------------+---------+

案例运行:一次成功的转账业务,账户1向账户2转账100元。

mysql> set @status = 0;
Query OK, 0 rows affected (0.00 sec)

mysql> source E:\sql\test.sql			# 用脚本执行,脚本中内容就是上面的代码
Query OK, 0 rows affected (0.00 sec)

mysql> call transfer(1, 2, 100, @status);
Query OK, 0 rows affected (0.10 sec)

mysql> select @status;
+---------+
| @status |
+---------+
|       0 |
+---------+
1 row in set (0.00 sec)

mysql> select * from bank_account;
+------------+---------+
| Account_Id | Balance |
+------------+---------+
|          1 |     400 |
|          2 |    1100 |
+------------+---------+
2 rows in set (0.00 sec)

案例运行:一次失败的转账业务,账户1向账户2转账1000元。

mysql> SET @status = 0;
Query OK, 0 rows affected (0.00 sec)

mysql> CALL transfer(1, 2, 1000, @status);
Query OK, 0 rows affected (0.11 sec)

mysql> SELECT @status;
+---------+
| @status |
+---------+
|      -1 |
+---------+
1 row in set (0.00 sec)

mysql> select * from bank_account;
+------------+---------+
| Account_Id | Balance |
+------------+---------+
|          1 |     500 |
|          2 |    1000 |
+------------+---------+
2 rows in set (0.00 sec)

7 触发器

7.1 创建触发器

触发器是数据库中的命名对象,与一个表关联,并且在该表上的 INSERT,UPDATE,DELETE 等更改操作前/后被触发。

触发器的典型应用

  • 实现自定义完整性约束
  • 用于值的计算
  • 日志或副本记录

让表上的增删改操作接受一定的强制性“规则”

使用 CREATE TRIGGER 语句创建触发

CREATE TRIGGER trigger_name						// 创建触发器
{ BEFORE | AFTER } { INSERT | UPDATE | DELETE }	// 触发的时机和事件
ON tbl_name FOR EACH ROW						// 触发器关联的表
trigger_body									// 触发器主体定义

example:
触发器示例1:将无效成绩“舍入”到有效成绩

delimiter $$

create trigger valid_score_before_update_choose
before update
on choose for each row
begin
	if New.score < 0 then set New.score = 0;
	elseif New.score > 100 then set New.score = 100;
	end if;
end $$

delimiter ;
mysql> update choose set score = 102 where Student_id = 'M20177006';
Query OK, 1 row affected (0.11 sec)
Rows matched: 1  Changed: 1  Warnings: 0

【MySQL数据库设计与应用(六)】存储程序(存储过程,存储函数,触发器,事件)_第4张图片

7.2 查看触发器的定义

查看触发器列表

  • SHOW TRIGGERS [{FROM | IN} db_name] [LIKE ‘pattern’ | WHERE expr]

【MySQL数据库设计与应用(六)】存储程序(存储过程,存储函数,触发器,事件)_第5张图片
查看定义存储例程的 CREATE 语句
【MySQL数据库设计与应用(六)】存储程序(存储过程,存储函数,触发器,事件)_第6张图片

7.3 删除触发器

使用 DROP TRIGGER 语句删除触发器

  • DROP TRIGGER [IF EXISTS] [schema_name.]trigger_name

example:

mysql> drop trigger if exists valid_score_before_update_choose;
Query OK, 0 rows affected (0.00 sec)

注意

  • 修改表名后,该表上的触发器仍然有效;
  • 删除表后,该表上创建的触发器会自动被删除。

8 用事件定时执行任务

8.1 事件的概念和基本属性

事件是指在 MySQL 事件调度器的调度下,在特定的时刻所执行的任务,因此也称为调度事件。

(一)事件调度器配置

  • 全局变量 event_scheduler 代表事件调度器的状态
    • SHOW VARIABLES LIKE ‘event_scheduler’;
      其值可以为 ON、OFF 或 DISABLED,代表启动、停止和禁用。
    • 可使用命令行参数或 my.ini 配置其为 DISABLED,且运行时不可改变
    • 未配置为 DISABLED 时,可以在运行时切换启动和停止
      SET GLOBAL event_scheduler = ON(或OFF);

事件也是一种存储程序,也有和其它存储程序相似的属性:

  • 存储程序的共性属性
    • 名称
    • 属于哪个数据库
    • 要执行的 SQL 语句
  • 和触发器相似的属性
    • 定义者(类似于触发器,有定义者,没有调用者)
  • 事件所特有的属性
    • 调度的时间和周期(类似于触发器的触发事件)

(二)调度的时间和周期

  • 在什么时间调度
    • 仅调度一次的任务在什么时间
    • 重复调度的事件,首次调度在什么时间
  • 每隔多长时间重复调度
    • 是否需要在某个时间后就不再重复调度
  • 过期的事件是否要自动删除

8.2 创建事件

  • 使用 CREATE EVENT 语句创建事件

【MySQL数据库设计与应用(六)】存储程序(存储过程,存储函数,触发器,事件)_第7张图片

CREATE EVENT event_name									// 创建事件
ON SCHEDULE												// 调度事件的时机
	{ AT time_spec										// 一次性事件的时刻
	| EVERY interval [STARTS time_spec] [ENDS time_spec] }	// 重复事件的周期和始终
[ ON COMPLETION [NOT] PRESERVE ]						// 完成后是否保留
[ ENABLE | DISABLE ]									// 创建时启用还是禁用
DO event_body;											// 事件要执行的

example:
示例 1:定义一个一次性事件,在一分钟后备份表。

CREATE EVENT event_backup
ON SCHEDULE
AT CURRENT_TIMESTAMP + INTERVAL 1 MINUTE
DO INSERT INTO t_bak SELECT * FROM t;

说明:

  • 在默认数据库中创建一个事件,名为 event_backup
  • 事件是一个一次性事件(使用了 AT 子句)
  • 事件在当前时间(CREATE EVENT 语句执行时)后 1 分钟被调度
  • 事件接受两个默认选项:创建后启用,完成后不予保留
  • 事件完成的任务是 DO 后面的一条语句

示例 2:定义一个重复性事件,在每天 1 点定时 备份表。

CREATE EVENT daily_backup
ON SCHEDULE
EVERY 1 DAY STARTS CURRENT_DATE + INTERVAL 1 HOUR
DO INSERT INTO t_bak SELECT * FROM t;

说明:

  • 在默认数据库中创建一个事件,名为 daily_backup
  • 事件是一个重复性事件(使用了 EVERY 子句)
  • 事件在的运行周期为 1 天(EVERY 1 DAY)
  • 起始时间是当天 1 点(STARTS CURRENT_DATE + INTERVAL 1 HOUR)
  • 事件将持续循环(没有使用 ENDS 指定终止时间)

8.3 查看事件的定义

查看事件列表
【MySQL数据库设计与应用(六)】存储程序(存储过程,存储函数,触发器,事件)_第8张图片
查看定义事件的 CREATE 语句
【MySQL数据库设计与应用(六)】存储程序(存储过程,存储函数,触发器,事件)_第9张图片

8.4 修改事件的属性和定义

使用 ALTER EVENT 语句

  • 不需要先删除后重新创建
  • 语句中的成分和 CREATE EVENT 非常相似
  • 增加了 RENAME TO 子句,用于修改事件名称
alter event daily_backup
on schedule
every 1 week starts current_date + interval 2 hour
rename to weekly_backup;

8.5 删除事件

使用 DROP EVENT 语句删除事件

  • DROP EVENT [IF EXISTS] [schema_name.]event_name

example:

DROP EVENT IF EXISTS weekly_backup;

你可能感兴趣的:(MySQL)