文章目录
【 1. 存储过程 PROCEDURE 】
1.1 存储过程原理
- 问题背景
我们前面所学习的 MySQL 语句都是针对一个表或几个表的单条 SQL 语句,但是在数据库的实际操作中,经常会有需要多条 SQL 语句处理多个表才能完成的操作。
例如,为了确认学生能否毕业,需要同时查询学生档案表、成绩表和综合表,此时就需要使用多条 SQL 语句来针对这几个数据表完成处理要求。
- 解决方法
存储过程是一组为了完成特定功能的 SQL 语句和一些特殊的控制结构的集合,一个存储过程是是一个可编程的函数。使用存储过程的目的是将常用或复杂的工作预先用 SQL 语句写好并用一个指定名称存储起来,这个过程经编译、优化后存储在数据库服务器中,因此称为 存储过程 。当以后需要数据库提供与已定义好的存储过程的功能相同的服务时,只需调用 “CALL 存储过程名字” 即可自动完成。
存储过程可以用来转换数据、数据迁移、制作报表,它类似于编程语言,一次执行成功,就可以随时被调用,完成指定的功能操作。 - 直接多条 SQL 语句 和 存储过程的区别
常用操作数据库的 SQL 语句在执行的时候需要先编译,然后执行。存储过程则采用另一种方式来执行 SQL 语句,存储过程是编译和优化后的。 - 存储过程的多语言、多平台的适用性
当希望在不同的应用程序或平台上执行相同的特定功能时,存储过程尤为合适:对于调用者来说,存储过程封装了 SQL 语句,调用者无需考虑逻辑功能的具体实现过程。只是简单调用即可,它可以被 Java 和 C# 等编程语言调用。
MySQL 5.0 版本以前并不支持存储过程,这使 MySQL 在应用上大打折扣。MySQL 从 5.0 版本开始支持存储过程,既提高了数据库的处理速度,同时也提高了数据库编程的灵活性
存储过程的优点 | 详述 |
---|---|
封装性 | 通常完成一个逻辑功能需要多条 SQL 语句,而且各个语句之间很可能传递参数,所以,编写逻辑功能相对来说稍微复杂些,而存储过程可以把这些 SQL 语句包含到一个独立的单元中,使外界看不到复杂的 SQL 语句,只需要简单调用即可达到目的。并且数据库专业人员可以随时对存储过程进行修改,而不会影响到调用它的应用程序源代码。 |
可增强 SQL 语句的功能和灵活性 | 存储过程可以用流程控制语句编写,有很强的灵活性,可以完成复杂的判断和较复杂的运算。 |
可减少网络流量 | 由于存储过程是在服务器端运行的,且执行速度快,因此当客户计算机上调用该存储过程时,网络中传送的只是该调用语句,从而可降低网络负载。 |
高性能 | 当存储过程被成功编译后,就存储在数据库服务器里了,以后客户端可以直接调用,这样所有的 SQL 语句将从服务器执行,从而提高性能。但需要说明的是,存储过程不是越多越好,过多的使用存储过程反而影响系统性能。 |
提高数据库的安全性和数据的完整性 | 存储过程提高安全性的一个方案就是把它作为中间组件,存储过程里可以对某些表做相关操作,然后存储过程作为接口提供给外部程序。这样,外部程序无法直接操作数据库表,只能通过存储过程来操作对应的表,因此在一定程度上,安全性是可以得到提高的。 |
使数据独立 | 数据的独立可以达到解耦的效果,也就是说,程序可以调用存储过程,来替代执行多条的 SQL 语句。这种情况下,存储过程把数据同用户隔离开来,优点就是当数据表的结构改变时,调用表不用修改程序,只需要数据库管理者重新编写存储过程即可。 |
1.2 创建存储过程 CREATE PROCEDURE
基本语法
- 在创建存储过程时,必须具有 CREATE ROUTINE 权限。
- 基本语法
- 过程名 :存储过程的名称,默认在当前数据库中创建。
- 若需要在特定数据库中创建存储过程,则要在名称前面加上数据库的名称,即 db_name.sp_name。
- 名称应当尽量避免选取与 MySQL 内置函数相同的名称,否则会发生错误。
- 过程参数:存储过程的参数列表。其中,<参数名>为参数名,<类型>为参数的类型(可以是任何有效的 MySQL 数据类型)。
- 当有多个参数时,参数列表中彼此间用逗号分隔;存储过程可以没有参数(此时存储过程的名称后仍需加上一对括号),也可以有 1 个或多个参数。
- MySQL 存储过程支持三种类型的参数,即:IN 输入参数、OUT 输出参数和 INOUT 输入/输出参数。其中, 输入参数 可以 传递给一个存储过程, 输出参数 用于 存储过程需要返回一个操作结果 的情形,而 输入/输出参数 既可以 充当输入参数也可以充当输出参数。
- 需要注意的是,参数的取名不要与数据表的列名相同,否则尽管不会返回出错信息,但是存储过程的 SQL 语句会将参数名看作列名,从而引发不可预知的结果。
- 过程体
存储过程的主体部分,也称为存储过程体,包含在过程调用的时候必须执行的 SQL 语句,这个部分以关键字 BEGIN 开始,以关键字 END 结束,若存储过程体中只有一条 SQL 语句,则可以省略 BEGIN-END 标志。
- 过程名 :存储过程的名称,默认在当前数据库中创建。
CREATE PROCEDURE <过程名> ( [过程参数[,…] ] )
BEGIN
<过程体>
END
DELIMITER 命令
- 问题背景
在 MySQL 中,服务器处理 SQL 语句默认是以分号作为语句结束标志的。然而,在创建存储过程时,存储过程体可能包含有多条 SQL 语句,这些 SQL 语句如果仍以分号作为语句结束符,那么 MySQL 服务器在处理时会以遇到的第一条 SQL 语句结尾处的分号作为整个程序的结束符,而不再去处理存储过程体中后面的 SQL 语句,这样显然不行。 - 解决方法:DELIMITER 命令
通常使用 DELIMITER 命令(中文译为 分隔符)将结束命令修改为其他字符。
基本语法- $$ 是用户定义的结束符,通常这个符号可以是一些特殊的符号,如两个“?”或两个“¥”等。
- 当使用 DELIMITER 命令时,应该 避免使用反斜杠“\”字符,因为它是 MySQL 的转义字符。
DELIMITER $$
- 实例1
在 MySQL 命令行客户端输入如下 SQL 语句,成功执行这条 SQL 语句后,任何命令、语句或程序的结束标志就换为两个问号“??”了。
mysql > DELIMITER ??
若希望换回默认的分号“;”作为结束标志,则在 MySQL 命令行客户端输入下列语句即可:DELIMITER 和分号“;”之间一定要有一个空格。
DELIMITER ;
- 实例2
创建名称为 ShowStuScore 的存储过程,存储过程的作用是从学生成绩信息表中查询学生的成绩信息。
DELIMITER //
CREATE PROCEDURE ShowStu()
BEGIN
SELECT * FROM tb_student_info;
END //
- 实例3
创建名称为 GetScoreByStu 的存储过程,输入参数是学生姓名。存储过程的作用是通过输入的学生姓名从学生成绩信息表中查询指定学生的性别信息。
DELIMITER //
CREATE PROCEDURE GetScoreByStu (IN student_name VARCHAR(30))
BEGIN
SELECT sex FROM tb_student_info WHERE name =student_name;
END //
1.3 查看存储过程
1.3.1 查看存储过程的状态 SHOW PROCEDURE STATUS
- SHOW STATUS 语句只能查看存储过程是操作的哪一个数据库、存储过程的名称、类型、谁定义的、创建和修改时间、字符编码等信息。基本语法
- LIKE 存储过程名用来匹配存储过程的名称,LIKE 不能省略。
SHOW PROCEDURE STATUS LIKE 存储过程名;
- 实例
- 创建数据表 studentinfo。
CREATE TABLE studentinfo (
ID int(11) NOT NULL,
NAME varchar(20) DEFAULT NULL,
SCORE decimal(4,2) DEFAULT NULL,
SUBJECT varchar(20) DEFAULT NULL,
TEACHER varchar(20) DEFAULT NULL,
PRIMARY KEY (ID)
);
- 向数据表 studentinfo 中插入数据。
INSERT INTO studentinfo(id,name,score) VALUES(1,"zhangsan",80),(2,"lisi","70");
- 创建存储过程 showstuscore。
DELIMITER //
CREATE PROCEDURE showstuscore()
BEGIN
SELECT id,name,score FROM studentinfo;
END //
- 查询名为 showstuscore 的存储过程的状态。
查询结果显示了存储过程的创建时间、修改时间和字符集等信息。
SHOW PROCEDURE STATUS LIKE 'showstuscore' \G
SHOW PROCEDURE STATUS LIKE 'show%' \G
- 创建数据表 studentinfo。
1.3.2 查看存储过程的定义 SHOW CREATE PROCEDURE
- SHOW CREATE 语句能查询存储过程的集体定义。
基本语法
SHOW CREATE PROCEDURE 存储过程名;
- 实例
使用 SHOW CREATE 查询名为 showstuscore 的存储过程的状态。
查询结果显示了存储过程的定义和字符集信息等。
SHOW CREATE PROCEDURE showstuscore \G
存储过程的位置: information_schema.Routines 表
- 在 information_schema 数据库下的 routines 表中,存储着所有存储过程的定义。所以,使用 SELECT 语句查询 routines 表中的存储过程和函数的定义时,一定要使用 routine_name 字段指定存储过程的名称,否则,将查询出所有的存储过程的定义。
SELECT * FROM information_schema.Routines WHERE ROUTINE_NAME=存储过程名;
1.4 修改存储过程 ALTER PROCEDURE
- ALTER PROCEDURE 语句仅可用于修改存储过程的某些特征。
如果要修改存储过程的内容,可以先删除原存储过程,再以相同的命名创建新的存储过程;
如果要修改存储过程的名称,也是先删除原存储过程,再以不同的命名创建新的存储过程。 - 基本语法
- 特征指定了存储过程的特性,可能的取值有:
- CONTAINS SQL 表示子程序包含 SQL 语句,但不包含读或写数据的语句。
- NO SQL 表示子程序中不包含 SQL 语句。
- READS SQL DATA 表示子程序中包含读数据的语句。
- MODIFIES SQL DATA 表示子程序中包含写数据的语句。
- SQL SECURITY { DEFINER |INVOKER } 指明谁有权限来执行。
- DEFINER 表示只有定义者自己才能够执行。
- INVOKER 表示调用者可以执行。
- COMMENT ‘string’ 表示注释信息。
- 特征指定了存储过程的特性,可能的取值有:
ALTER PROCEDURE 存储过程名 [ 特征 ... ]
- 实例
- 修改存储过程 showstuscore 的定义,将读写权限改为 MODIFIES SQL DATA,并指明调用者可以执行。
ALTER PROCEDURE showstuscore MODIFIES SQL DATA SQL SECURITY INVOKER;
- 查看修改后的信息
结果显示,存储过程修改成功。从运行结果可以看到,访问数据的权限已经变成了 MODIFIES SQL DATA,安全类型也变成了 INVOKE。
SHOW CREATE PROCEDURE showstuscore \G
- 修改存储过程 showstuscore 的定义,将读写权限改为 MODIFIES SQL DATA,并指明调用者可以执行。
1.5 删除存储过程 DROP PROCEDURE
- 存储过程被创建后,就会一直保存在数据库服务器上,直至被删除。当 MySQL 数据库中存在废弃的存储过程时,我们需要将它从数据库中删除。
基本语法- 过程名:指定要删除的存储过程的名称。
- IF EXISTS:指定这个关键字,用于防止因删除不存在的存储过程而引发的错误。
- 存储过程名称后面没有参数列表,也没有括号,在删除之前,必须确认该存储过程没有任何依赖关系,否则会导致其他与之关联的存储过程无法运行。
DROP PROCEDURE [ IF EXISTS ] <过程名>
- 实例
- 删除存储过程 ShowStuScore。
DROP PROCEDURE ShowStuScore;
- 通过查询 information_schema 数据库下的 routines 表来确认上面的删除是否成功。
SELECT * FROM information_schema.routines WHERE routine_name='ShowStuScore';
- 删除存储过程 ShowStuScore。
1.6 调用存储过程 CALL
- 执行存储过程和存储函数需要拥有 EXECUTE 执行权限 (EXECUTE 权限的信息存储在 information_schema 数据库下的 USER_PRIVILEGES 表中)。
- MySQL 中使用 CALL 语句来调用存储过程,调用存储过程后,数据库系统将执行存储过程中的 SQL 语句,然后将结果返回给输出值。
基本语法- CALL 语句接收存储过程的名字以及需要传递给它的任意参数。
- sp_name 表示存储过程的名称,parameter 表示存储过程的参数。
- 因为 存储过程实际上也是一种函数,所以存储过程名后需要有( )符号 ,即使不传递参数也需要。
CALL sp_name([parameter[...]]);
- 实例
调用 1.3查看存储过程 中创建的 ShowStu 存储过程。
CALL ShowStu();
【 2. 存储函数 FUNCTION 】
- 存储函数和存储过程一样,都是在数据库中定义一些 SQL 语句的集合。
存储函数可以通过 return 语句返回函数值,主要用于计算并返回一个值。而存储过程没有直接返回值,主要用于执行操作。
2.1 存储函数的创建 CREATE FUNCTION
- 基本语法
- sp_name 参数:表示存储函数的名称;
- func_parameter:表示存储函数的参数列表。func_parameter 可以由多个参数组成,其中每个参数由参数名称和参数类型组成,其形式如下:
[IN | OUT | INOUT] param_name type;
- IN 表示输入参数,OUT 表示输出参数,INOUT 表示既可以输入也可以输出;
- param_name 参数是存储函数的参数名称;
- type 参数指定存储函数的参数类型,该类型可以是 MySQL 数据库的任意数据类型。
- RETURNS type:指定返回值的类型;如果在存储函数中的 RETURN 语句返回一个类型不同于函数的 RETURNS 子句中指定类型的值,返回值将被强制为恰当的类型。
比如,如果一个函数返回一个 ENUM 或 SET 值,但是 RETURN 语句返回一个整数,对于 SET 成员集的相应的 ENUM 成员,从函数返回的值是字符串。 - characteristic 参数:指定存储函数的特性,该参数的取值与存储过程是一样的;
- routine_body 参数:表示 SQL 代码的内容,可以用 BEGIN…END 来标示 SQL 代码的开始和结束。
- 在具体创建函数时,函数名不能与已经存在的函数名重名。除了上述要求外,推荐函数名命名(标识符)为 function_xxx 或者 func_xxx。
CREATE FUNCTION sp_name ([func_parameter[...]])
RETURNS type
[characteristic ...] routine_body
- 实例1
使用 CREATE FUNCTION 创建查询 tb_student 表中某个学生姓名的函数。
代码创建 func_student 函数,该函数拥有一个类型为 INT(11) 的参数 id,返回值为 VARCHAR(20) 类型。SELECT 语句从 tb_student 表中查询 id 字段值等于所传入参数 id 值的记录,同时返回该条记录的 name 字段值。
DELIMITER //
CREATE FUNCTION func_student_info(id INT(11))
RETURNS VARCHAR(25)
COMMENT '查询某个学生的姓名'
BEGIN
RETURN(SELECT name FROM tb_student_info WHERE tb_student_info.id = id);
END //
DELIMITER ;
2.2 存储函数的其他操作
- 操作存储函数和操作存储过程不同的是将 PROCEDURE 替换成了 FUNCTION,存储函数的特征与存储过程的基本一样。由于存储函数和存储过程的查看、修改、删除等操作几乎相同,所以我们不再详细讲解如何操作存储函数了。
- 查看存储函数的语法如下:
SHOW FUNCTION STATUS LIKE 存储函数名;
SHOW CREATE FUNCTION 存储函数名;
SELECT * FROM information_schema.Routines WHERE ROUTINE_NAME=存储函数名;
- 修改存储函数的语法如下:
ALTER FUNCTION 存储函数名 [ 特征 ... ]
- 删除存储过程的语法如下:
DROP FUNCTION [ IF EXISTS ] <函数名>
2.3 调用存储函数 SELECT
- 在 MySQL 中,存储函数的使用方法与 MySQL 内部函数的使用方法相同。用户自己定义的存储函数与 MySQL 内部函数是一个性质的。区别在于,存储函数是用户自己定义的,而内部函数是 MySQL 开发者定义的。
- 实例
调用 2.1 MySQL创建存储函数 中创建的存储函数 func_student_info()
SELECT func_student_info(3);
【 3. 变量 】
- 在 MySQL 中,除了支持标准的存储过程和函数外,还引入了表达式。表达式与其它高级语言的表达式一样,由变量、运算符和流程控制来构成。
变量 是表达式语句中最基本的元素,可以用来临时存储数据,在存储过程和函数中都可以定义和使用变量。用户可以使用 DECLARE 关键字 (中文译为声明)来定义变量,定义后可以为变量赋值,这些变量的作用范围是 BEGIN…END 程序段中。
3.1 定义变量
- 基本语法
- DECLARE 关键字是用来声明变量的;
- var_name 参数是变量的名称,这里可以同时定义多个变量;
- type 参数用来指定变量的类型;
- DEFAULT value 子句将变量默认值设置为 value,没有使用 DEFAULT 子句时,默认值为 NULL。
DECLARE var_name[,...] type [DEFAULT value]
- 实例
定义变量 my_sql,数据类型为 INT 类型,默认值为 10。
DECLARE my_sql INT DEFAULT 10;
3.2 为变量赋值
- 当将查询结果赋值给变量时,该查询语句的返回结果只能是单行 。
SET…=… 为变量赋值
- 基本语法
- SET 关键字用来为变量赋值;
- var_name 参数是变量的名称;
- expr 参数是赋值表达式。
- 一个 SET 语句可以同时为多个变量赋值,各个变量的赋值语句之间用逗号隔开。
SET var_name = expr[,var_name = expr]...
- 实例
为变量 my_sql 赋值为 30。
SET my_sql=30;
SELECT…INTO 为变量赋值
- 基本语法
- col_name 参数表示查询的字段名称;
- var_name 参数是变量的名称;
- table_name 参数指表的名称;
- condition 参数指查询条件。
SELECT col_name [...] INTO var_name[,...]
FROM table_name WEHRE condition
- 实例
从 tb_student 表中查询 id 为 2 的记录,将该记录的 id 值赋给变量 my_sql。
SELECT id INTO my_sql FROM tb_student WEHRE id=2;
【 4. 定义条件和处理程序 】
- 问题背景
在程序的运行过程中可能会遇到问题,此时我们可以通过定义条件和处理程序来事先定义这些问题。
定义条件 是指 事先定义程序执行过程中遇到的问题, 处理程序 定义了 在遇到这些问题时应当采取的处理方式和解决办法,保证存储过程和函数在遇到警告或错误时能继续执行 ,从而增强程序处理问题的能力,避免程序出现异常被停止执行。
4.1 定义条件 CONDITION
- 基本语法
- condition_name 参数表示 条件的名称;
- condition_value 参数表示 条件的类型;condition_value 可以是以下两种:
- sqlstate_value 表示长度为 5 的字符串类型错误代码,
- mysql_error_code 表示数值类型错误代码。
例如 ERROR 1146(42S02) 中,sqlstate_value 值是 42S02,mysql_error_code 值是 1146。
DECLARE condition_name CONDITION FOR condition_value
- 实例
定义“ERROR 1146 (42S02)”这个错误,名称为 can_not_find。 可以用两种不同的方法来定义。
//方法一:使用sqlstate_value
DECLARE can_not_find CONDITION FOR SQLSTATE '42S02';
或
//方法二:使用 mysql_error_code
DECLARE can_not_find CONDITION FOR 1146;
4.2 定义处理程序 HANDLER
- 基本语法
- handler_type 参数指明错误的处理方式,该参数有 3 个取值,分别是 CONTINUE、EXIT 和 UNDO。
- CONTINUE 表示遇到错误不进行处理,继续向下执行;
- EXIT 表示遇到错误后马上退出;
- UNDO 表示遇到错误后撤回之前的操作,MySQL 中暂时还不支持这种处理方式。因此,遇到错误时最好执行 EXIT 退出操作。如果事先能够预测错误类型,并且进行相应的处理,那么可以执行 CONTINUE 操作 。
- condition_value 参数指明错误类型,该参数有 6 个取值:
- SQLSTATE [VALUE] sqlstate_value:包含 5 个字符的字符串错误值;
- condition_name:表示 DECLARE 定义的错误条件名称;
- SQLWARNING:匹配所有以 01 开头的 sqlstate_value 值;
- NOT FOUND:匹配所有以 02 开头的 sqlstate_value 值;
- SQLEXCEPTION:匹配所有没有被 SQLWARNING 或 NOT FOUND 捕获的 sqlstate_value 值;
- mysql_error_code:匹配数值类型错误代码。
- handler_type 参数指明错误的处理方式,该参数有 3 个取值,分别是 CONTINUE、EXIT 和 UNDO。
DECLARE handler_type HANDLER FOR condition_value[...] sp_statement
- 实例:定义处理程序的几种方式
定义处理程序的方式 | 代码 | 描述 |
---|---|---|
捕获 sqlstate_value | DECLARE CONTINUE HANDLER FOR SQLSTATE ‘42S02’ SET @info=‘CAN NOT FIND’; | 捕获 sqlstate_value 值。如果遇到 sqlstate_value 值为 42S02,执行 CONTINUE 操作,并且输出“CAN NOT FIND”信息。 |
捕获 mysql_error_code | DECLARE CONTINUE HANDLER FOR 1146 SET @info=‘CAN NOT FIND’; | 捕获 mysql_error_code 值。如果遇到 mysql_error_code 值为 1146, 执行 CONTINUE 操作,并且输出“CAN NOT FIND”信息。 |
先定义条件,然后调用 | DECLARE can_not_find CONDITION FOR 1146; DECLARE CONTINUE HANDLER FOR can_not_find SET @info=‘CAN NOT FIND’; | 先定义条件,然后再调用条件。这里先定义 can_not_find 条件,遇到 1146 错误就执行 CONTINUE 操作。 |
使用 SQLWARNING | DECLARE EXIT HANDLER FOR SQLWARNING SET @info=‘ERROR’; | 使用 SQLWARNING。SQLWARNING 捕获所有以 01 开头的 sqlstate_value 值,然后执行 EXIT 操作,并且输出“ERROR"信息。 |
使用 NOT FOUND | DECLARE EXIT HANDLER FOR NOT FOUND SET @info=‘CAN NOT FIND’; | 使用 NOT FOUND。NOT FOUND 捕获所有以 02 开头的 sqlstate_value 值,然后执行 EXIT 操作,并且输出“CAN NOT FIND”信息。 |
使用 SQLEXCEPTION | DECLARE EXIT HANDLER FOR SQLEXCEPTION SET @info=‘ERROR’; | 使用 SQLEXCEPTION。 SQLEXCEPTION 捕获所有没有被 SQLWARNING 或 NOT FOUND 捕获的 sqlstate_value 值,然后执行 EXIT 操作,并且输出“ERROR”信息。 |
- 实例
- 定义条件和处理顺序。
如果 DECLARE CONTINUE HANDLER FOR SQLSTATE ‘23000’ SET @X2=1; 这一行不存在,第二个 INSERT 因 PRIMARY KEY 约束而失败之后,MySQL 可能已经采取 EXIT 策略,且 SELECT @X 可能已经返回 2。
CREATE PROCEDURE handlerdemo()
BEGIN
DECLARE CONTINUE HANDLER FOR SQLSTATE '23000' SET @X2=1;
SET @X=1;
INSERT INTO t8 VALUES(1);
SET @X=2;
INSERT INTO t8 VALUES(1);
SET @X=3;
END //
- 将语句结束标记改为分号
DELIMITER ;
- 调用处理过程
CALL handlerdemo();
- 显示 变量 X 的值。
执行结果 @X 等于 3,这表明 MySQL 执行到程序的末尾。
SELECT @X;
- 定义条件和处理顺序。
- @X 表示用户变量,使用 SET 语句为其赋值,用户变量与连接有关,一个客户端定义的变量不能被其他客户端所使用,当客户端退出时,该客户端连接的所有变量将自动释放。
【 5. 游标 Cursor 】
- 问题背景
关系数据库管理系统实质是 面向集合 的,在 MySQL 中并没有一种描述表中单一记录的表达形式,除非使用 WHERE 子句来限制只有一条记录被选中 。
在 MySQL 中,存储过程或函数中的查询有时会返回多条记录,而使用简单的 SELECT 语句,没有办法得到第一行、下一行或前十行的数据,这时可以使用 游标(也有的称为光标) 来 逐条读取查询结果集中的记录,一般通过游标定位到结果集的某一行进行数据修改。个人理解游标就是一个标识,用来标识数据取到了什么地方,可以把它理解成数组中的下标。 - 不像多数 DBMS,MySQL 游标只能用于存储过程和函数 。
5.1 声明游标
- MySQL 中使用 DECLARE 关键字来声明游标,并定义相应的 SELECT 语句,根据需要添加 WHERE 和其它子句。
基本语法- cursor_name 表示游标的名称;
- select_statement 表示 SELECT 语句,可以返回一行或多行数据。
DECLARE cursor_name CURSOR FOR select_statement;
- 实例
声明一个名为 nameCursor 的游标。
DELIMITER //
CREATE PROCEDURE processnames()
BEGIN
DECLARE nameCursor CURSOR
FOR
SELECT name FROM tb_student;
END//
5.2 打开游标
- 声明游标之后,要想从游标中提取数据,必须首先打开游标 。
基本语法- cursor_name 表示所要打开游标的名称。
- 需要注意的是,打开一个游标时,游标并不指向第一条记录,而是指向第一条记录的前边。
- 在程序中,一个游标可以打开多次。用户打开游标后,其他用户或程序可能正在更新数据表,所以有时会导致用户每次打开游标后,显示的结果都不同。
OPEN cursor_name;
5.3 使用游标
- 游标顺利打开后,可以使用 FETCH…INTO 语句来读取数据。
基本语法:- 将 cursor_name 游标中 SELECT 语句的执行结果保存到变量参数 var_name 中。
- 变量参数 var_name 必须在游标使用之前定义。
- 使用游标类似高级语言中的数组遍历,当第一次使用游标时,此时游标指向结果集的第一条记录;MySQL 的游标是只读的,也就是说,我们只能顺序地从开始往后读取结果集,不能从后往前,也不能直接跳到中间的记录。
FETCH cursor_name INTO var_name [,var_name]...
5.4 关闭游标
- CLOSE 会释放游标使用的所有内部内存和资源,因此每个游标不再需要时都应该关闭;如果 不明确关闭游标,MySQL 将会在到达 END 语句时自动关闭它 。
基本语法
CLOSE cursor_name;
实例
- 创建 users 数据表
CREATE TABLE users
(
ID BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
user_name VARCHAR(60),
user_pass VARCHAR(64),
PRIMARY KEY (ID)
);
- 插入数据
INSERT INTO users VALUES(null,'sheng','sheng123'),
(null,'yu','yu123'),
(null,'ling','ling123');
- 创建存储过程 test_cursor,并创建游标 cur_test。
DELIMITER // # 改语句结束标志为 //
CREATE PROCEDURE test_cursor (in param INT(10),out result VARCHAR(90))
BEGIN
DECLARE name VARCHAR(20);
DECLARE pass VARCHAR(20);
DECLARE done INT;
DECLARE cur_test CURSOR FOR SELECT user_name,user_pass FROM users; #声明游标,指定数据集为users数据库中的user_name,user_pass字段
DECLARE continue handler FOR SQLSTATE '02000' SET done = 1;
IF param
THEN SELECT id INTO result FROM users WHERE id = param;#若输入参数非空,则将user数据库中id字段值等于输入参数param的记录赋值给输出结果result
ELSE # 若输入参数为空
OPEN cur_test; #打开游标
repeat #循环
FETCH cur_test into name,pass; #将游标当前索引的数据保存在变量里
SELECT concat_ws(',',result,name,pass) INTO result;#对于查询结果中的每一行,将result、name和pass列的值用逗号连接起来,然后将这个组合后的字符串赋值给变量result
until done
END repeat; #结束循环
CLOSE cur_test; #关闭游标
END IF; #结束IF
END // #结束存储过程
- 查询 users 数据表中的第 3 条记录,结果保存在test中,显示test。
call test_cursor(3,@test)//
select @test//
- 创建 pro_users() 存储过程,定义 cur_1 游标,将表 users 中的 user_name 字段全部修改为 MySQL。
CREATE PROCEDURE pro_users()
BEGIN
DECLARE result VARCHAR(100);
DECLARE no INT;
DECLARE cur_1 CURSOR FOR SELECT user_name FROM users;#声明游标,指定数据集为users数据库中的user_name字段
DECLARE CONTINUE HANDLER FOR NOT FOUND SET no=1;
SET no=0;
OPEN cur_1;#打开游标
WHILE no=0 do #若no=0则进行循环
FETCH cur_1 into result;#将当前游标cur_1检索到的数据赋值给result
UPDATE users SET user_name='MySQL' WHERE user_name=result;#将users数据库中user_name字段值=result的记录user_name都改为MySQL
END WHILE;
CLOSE cur_1;
END //
- 调用存储过程,结果显示,users 表中的 user_name 字段已经全部修改为 MySQL。
call pro_users() //
SELECT * FROM users //
【 6. 流程控制语句 】
- 在存储过程和自定义函数中可以使用流程控制语句来控制程序的流程。
6.1 IF THEN 条件判断
- IF 语句用来进行条件判断,根据是否满足条件(可包含多个条件),来执行不同的语句,是流程控制中最常用的判断语句。
- 基本语法
- search_condition 参数表示条件判断语句,如果返回值为 TRUE ,相应的 SQL 语句 statement_list 列表被执行(statement_list 可以包括一个或多个语句);如果返回值为 FALSE,则 ELSE 子句的语句列表被执行。
- IF 语句都需要使用 END IF 来结束。
IF search_condition THEN statement_list
[ELSEIF search_condition THEN statement_list]...
[ELSE statement_list]
END IF
- 实例
该示例根据 age 与 20 的大小关系来执行不同的 SET 语句。如果 age 值大于20,那么将 count1 的值加 1;如果 age 值等于 20,那么将 count2 的值加 1;其他情况将 count3 的值加 1。
IF age>20 THEN SET @count1=@count1+1;
ELSEIF age=20 THEN @count2=@count2+1;
ELSE @count3=@count3+1;
END lF;
6.2 CASE 条件选择
- CASE 语句也是用来进行条件判断的,它提供了多个条件进行选择,可以实现比 IF 语句更复杂的条件判断。
- 基本语法1
- case_value 参数表示条件判断的变量,决定了哪一个 WHEN 子句会被执行;
- when_value 参数表示变量的取值,如果某个 when_value 表达式与 case_value 变量的值相同,则执行对应的 THEN 关键字后的 statement_list 中的语句;
- statement_list 参数表示 when_value 值没有与 case_value 相同值时的执行语句。
- CASE 语句都要使用 END CASE 结束。
CASE case_value
WHEN when_value THEN statement_list
[WHEN when_value THEN statement_list]...
[ELSE statement_list]
END CASE
- 基本语法2
与上述语句不同的是,该语句中的 WHEN 语句将被逐个执行,直到某个 search_condition 表达式为真,则执行对应 THEN 关键字后面的 statement_list 语句;如果没有条件匹配,ELSE 子句里的语句被执行。有点类似 多个if 语句。- search_condition 参数表示条件判断语句;
- statement_list 参数表示不同条件的执行语句。
CASE
WHEN search_condition THEN statement_list
[WHEN search_condition THEN statement_list] ...
[ELSE statement_list]
END CASE
- 实例
如果 age 值为 20,count1 的值加 1,否则 count2 的值加 1。
CASE age
WHEN 20 THEN SET @count1=@count1+1;
ELSE SET @count2=@count2+1;
END CASE;
或
CASE
WHEN age=20 THEN SET @count1=@count1+1;
ELSE SET @count2=@count2+1;
END CASE;
6.3 LOOP 循环
- LOOP 语句可以使某些特定的语句重复执行,与 IF 和 CASE 语句相比,LOOP 只实现了一个简单的循环,并不进行条件判断。类似C语言的 while。
- 基本语法
- begin_label 参数和 end_label 参数分别表示循环开始和结束的标志,这两个标志必须相同,而且都可以省略;
- statement_list 参数表示需要循环执行的语句。
- LOOP 语句本身没有停止循环的语句,必须使用 LEAVE 语句等才能停止循环,跳出循环过程 。
- LOOP 循环都以 LOOP 开始,END LOOP 结束。
[begin_label:]LOOP
statement_list
END LOOP [end_label]
或
LOOP
statement_list
END LOOP
- 实例
该示例循环执行 count 加 1 的操作,因为没有跳出循环的语句,这个循环成了一个死循环。
add_num:LOOP
SET @count=@count+1;
END LOOP add_num;
6.4 LEAVE 跳出循环
- LEAVE 语句主要用于 跳出循环控制。
基本语法- LEAVE 语句必须跟在循环标志前面。
- label 参数表示循环的标志;
LEAVE label
- 实例
循环执行 count 加 1 的操作,当 count 的值等于 100 时,跳出循环。
add_num:LOOP
SET @count=@count+1;
IF @count=100 THEN
LEAVE add_num;
END LOOP add num;
6.5 ITERATE 跳出本次循环
- ITERATE 是“再次循环”的意思,用来跳出本次循环,直接进入下一次循环。类似C语言中的 continue 。
基本语法- ITERATE 语句必须跟在循环标志前面。
- label 参数表示循环的标志。
ITERATE label
- 实例
执行 count 加 1 的操作,count 值为 100 时结束循环。如果 count 的值能够整除 3,则跳出本次循环,不再执行下面的 SELECT 语句。
add_num:LOOP
SET @count=@count+1;
IF @count=100 THEN
LEAVE add_num;
ELSE IF MOD(@count,3)=0 THEN
ITERATE add_num;
SELECT * FROM employee;
END LOOP add_num;
6.6 REPEAT UNTIL 先执行再判断是否结束循环
- REPEAT 语句是有条件控制的循环语句,每次语句执行完毕后,会对条件表达式进行判断,如果表达式返回值为 TRUE,则循环结束,否则重复执行循环中的语句。类似C语言中的 do…while。
基本语法- begin_label 为 REPEAT 语句的标注名称,该参数可以省略;
- REPEAT 语句内的语句被重复,直至 search_condition 返回值为 TRUE。
- statement_list 参数表示循环的执行语句;
- search_condition 参数表示结束循环的条件,满足该条件时循环结束。
- REPEAT 循环都用 END REPEAT 结束。
[begin_label:] REPEAT
statement_list
UNTIL search_condition
END REPEAT [end_label]
- 实例
循环执行 count 加 1 的操作,count 值为 100 时结束循环。
REPEAT
SET @count=@count+1;
UNTIL @count=100
END REPEAT;
6.7 WHILE DO 先判断是否进行循环再执行
- WHILE 语句也是有条件控制的循环语句。WHILE 语句和 REPEAT 语句不同的是,WHILE 语句是当 满足条件时执行循环内的语句,否则退出循环。类似C语言中的 while…do。
基本语法- search_condition 参数表示循环执行的条件,满足该条件时循环执行;
- statement_list 参数表示循环的执行语句。
- WHILE 循环需要使用 END WHILE 来结束。
[begin_label:] WHILE search_condition DO
statement list
END WHILE [end label]
- 实例
该示例循环执行 count 加 1 的操作,count 值小于 100 时执行循环。如果 count 值等于 100 了,则跳出循环。
WHILE @count<100 DO
SET @count=@count+1;
END WHILE;
【 7. 触发器 TRIGGER 】
7.1 触发器原理
- 触发器 和存储过程相似,都是嵌入到 MySQL 中的一段程序。不同的是执行存储过程要使用 CALL 语句来调用,而触发器的执行不需要使用 CALL 语句来调用,也不需要手工启动,触发器是由事件来触发某个操作,当数据库执行这些事件时,就会激活触发器来执行相应的操作 。在 MySQL 中,只有执行 INSERT、UPDATE 和 DELETE 操作时才能激活触发器 ,其它 SQL 语句则不会激活触发器。
比如当对 student 表进行操作(INSERT,DELETE 或 UPDATE)时就会激活它执行。 - 触发器与数据表关系密切,主要用于保护表中的数据。特别是当有多个表具有一定的相互联系的时候,触发器能够让不同的表保持数据的一致性。
那么为什么要使用触发器呢?比如,在实际开发项目时,我们经常会遇到以下情况:
- 在学生表中添加一条关于学生的记录时,学生的总数就必须同时改变。
- 增加一条学生记录时,需要检查年龄是否符合范围要求。
- 删除一条学生信息时,需要删除其成绩表上的对应记录。
- 删除一条数据时,需要在数据库存档表中保留一个备份副本。
虽然上述情况实现的业务逻辑不同,但是它们都需要在数据表发生更改时,自动进行一些处理。这时就可以使用触发器处理。例如,对于第一种情况,可以创建一个触发器对象,每当添加一条学生记录时,就执行一次计算学生总数的操作,这样就可以保证每次添加一条学生记录后,学生总数和学生记录数是一致的。
- 触发器的优缺点
优点 | 缺点 |
---|---|
1. 触发器的执行是自动的,当对触发器相关表的 数据做出相应的修改后立即执行。 2. 触发器可以实施比 FOREIGN KEY 约束、CHECK 约束更为复杂的检查和操作。 3. 触发器 可以实现表数据的级联更改,在一定程度上保证了数据的完整性。 | 1. 使用触发器实现的业务逻辑在出现问题时很难进行定位,特别是涉及到多个触发器的情况下,会使 后期维护变得困难。 2. 大量使用触发器容易导致代码结构被打乱,增加了程序的复杂性。 3. 如果需要变动的 数据量较大时,触发器的执行效率会非常低。 |
- MySQL 支持的触发器
MySQL 支持的触发器 | 响应位置 | 描述 |
---|---|---|
INSERT 触发器 | 在 INSERT 语句执行之前或之后响应的触发器。 例如:INSERT 的 BEFORE 触发器不仅能被 MySQL 的 INSERT 语句激活,也能被 LOAD DATA 语句激活。 | 1. 在 INSERT 触发器代码内,可引用一个名为 NEW (不区分大小写)的虚拟表来访问被插入的行。 2. 在 BEFORE INSERT 触发器中,NEW 中的值也可以被更新,即允许更改被插入的值(只要具有对应的操作权限)。 3. 对于 AUTO_INCREMENT 列,NEW 在 INSERT 执行之前包含的值是 0,在 INSERT 执行之后将包含新的自动生成值。 |
UPDATE 触发器 | 在 UPDATE 语句执行之前或之后响应的触发器。 例如:UPDATE 语句。 | 1. 在 UPDATE 触发器代码内,可引用一个名为 NEW(不区分大小写)的虚拟表来访问更新的值。 2. 在 UPDATE 触发器代码内,可引用一个名为 OLD(不区分大小写)的虚拟表来访问 UPDATE 语句执行前的值。 3. 在 BEFORE UPDATE 触发器中,NEW 中的值可能也被更新,即允许更改将要用于 UPDATE 语句中的值(只要具有对应的操作权限)。 4. OLD 中的值全部是只读的,不能被更新。 5. 当触发器设计对触发表自身的更新操作时,只能使用 BEFORE 类型的触发器,AFTER 类型的触发器将不被允许。 |
DELETE 触发器 | 在 DELETE 语句执行之前或之后响应的触发器。从表中删除某一行数据时激活触发器。 例如:DELETE 和 REPLACE 语句。 | 1. 在 DELETE 触发器代码内,可以引用一个名为 OLD(不区分大小写)的虚拟表来访问被删除的行。 2. OLD 中的值全部是只读的,不能被更新。 |
- 触发器使用的过程中,MySQL 会按照以下方式来处理错误:
- 对于事务性表,如果触发程序失败,以及由此导致的整个语句失败,那么该语句所执行的所有更改将回滚;对于非事务性表,则不能执行此类回滚,即使语句失败,失败之前所做的任何更改依然有效。
- 若 BEFORE 触发程序失败,则 MySQL 将不执行相应行上的操作。
- 若在 BEFORE 或 AFTER 触发程序的执行过程中出现错误,则将导致调用触发程序的整个语句失败。
- 仅当 BEFORE 触发程序和行操作均已被成功执行,MySQL 才会执行 AFTER 触发程序。
7.2 创建触发器 CREATE TRIGGER
基本语法
- 每个表都支持 INSERT、UPDATE 和 DELETE 的 BEFORE 与 AFTER,因此 每个表最多支持 6 个触发器,每个表的每个事件每次只允许有一个触发器。单一触发器不能与多个事件或多个表关联。
- 基本语法
- 触发器名。触发器的名称,触发器在当前数据库中必须具有唯一的名称。如果要在某个特定数据库中创建,名称前面应该加上数据库的名称。
- INSERT | UPDATE | DELETE
触发事件,用于指定激活触发器的语句的种类。 - BEFORE 和 AFTER,触发器被触发的时刻,表示触发器是在激活它的语句之前或之后触发。若希望验证新数据是否满足条件,则使用 BEFORE 选项;若希望在激活触发器的语句执行之后完成几个或更多的改变,则通常使用 AFTER 选项。
- 表名
与触发器相关联的表名,此表必须是永久性表 ,不能将触发器与临时表或视图关联起来。在该表上触发事件发生时才会激活触发器。同一个表不能拥有两个具有相同触发时刻和事件的触发器 。
例如,对于一张数据表,不能同时有两个 BEFORE UPDATE 触发器,但可以有一个 BEFORE UPDATE 触发器和一个 BEFORE INSERT 触发器,或一个 BEFORE UPDATE 触发器和一个 AFTER UPDATE 触发器。 - 触发器主体
触发器动作主体,包含触发器激活时将要执行的 MySQL 语句。如果要执行多个语句,可使用 BEGIN…END 复合语句结构。 - FOR EACH ROW
一般是指 行级触发,对于受触发事件影响的每一行都要激活触发器的动作。
例如,使用 INSERT 语句向某个表中插入多行数据时,触发器会对每一行数据的插入都执行相应的触发器动作。
CREATE <触发器名> < BEFORE | AFTER >
<INSERT | UPDATE | DELETE >
ON <表名> FOR EACH Row<触发器主体>
实例:创建 BEFORE 类型触发器
- 创建数据表 tb_emp8 为员工信息表,包含 id、name、deptId 和 salary 字段。
CREATE TABLE tb_emp8(
id INT(11),
name VARCHAR(25),
deptid INT(11),
salary float
);
查看数据表
SELECT * FROM tb_emp8;
DESC tb_emp8;
- 创建一个名为 SumOfSalary 的触发器,触发的条件是向数据表 tb_emp8 中插入数据之前,对新插入的 salary 字段值进行求和计算。
CREATE TRIGGER SumOfSalary
BEFORE INSERT ON tb_emp8
FOR EACH ROW
SET @sum=@sum+NEW.salary;
- 设置变量sum=0
SET @sum=0;
向表 tb_emp8 中插入记录,定义的 sum 值由 0 变成了 1500,即插入值 1000 和 500 的和。
INSERT INTO tb_emp8
VALUES(1,'A',1,1000),(2,'B',1,500);
查看结果
SELECT @sum;
实例:创建 AFTER 类型触发器
- 创建数据表 tb_emp6 和 tb_emp7 都为员工信息表,包含 id、name、deptId 和 salary 字段。
CREATE TABLE tb_emp6(
id INT(11),
name VARCHAR(25),
deptid INT(11),
salary float
);
CREATE TABLE tb_emp7(
id INT(11),
name VARCHAR(25),
deptid INT(11),
salary float
);
- 创建一个名为 double_salary 的触发器,触发的条件是向数据表 tb_emp6 中插入数据之后,再向数据表 tb_emp7 中插入相同的数据,并且 salary 为 tb_emp6 中新插入的 salary 字段值的 2 倍。
CREATE TRIGGER double_salary
AFTER INSERT ON tb_emp6
FOR EACH ROW
INSERT INTO tb_emp7
VALUES (NEW.id,NEW.name,deptId,2*NEW.salary);
- 触发器 double_salary 创建完成之后,向表 tb_emp6 中插入记录时,同时向表 tb_emp7 中插入相同的记录,并且 salary 字段为 tb_emp6 中 salary 字段值的 2 倍。
INSERT INTO tb_emp6 VALUES (1,'A',1,1000),(2,'B',1,500);
- 查看结果。
SELECT * FROM tb_emp6;
SELECT * FROM tb_emp7;
实例:创建UPDATE 类型触发器
- 首先创建一个数据表 account,表中有两个字段,分别是 INT 类型的 accnum 和 DECIMAL 类型的 amount。
CREATE TABLE account(
accnum INT(4),
amount DECIMAL(10,2));
创建一个数据表 myevent 数据表,表中有两个字段,分别是 INT 类型的 id 和 CHAR类型的 evtname 。
CREATE TABLE myevent(
id INT(11) DEFAULT NULL,
evtname CHAR(20) DEFAULT NULL);
- 创建一个名为 trigupdate 的触发器,每次 account 表更新数据之后都向 myevent 数据表中插入一条数据。
CREATE TRIGGER trigupdate AFTER UPDATE ON account
FOR EACH ROW INSERT INTO myevent VALUES(1,'after update');
- 向 account 表中插入记录,再更新记录。
INSERT INTO account VALUES (1,2.5);
向 account 表中更改记录,将 amount 字段所有的值都设置为3.14
UPDATE account SET amount=3.14;
- 查看 myevent 数据表。
SELECT *FROM myevent;
可以看到,触发器被触发,数据表中有了一条记录。
7.3 查看触发器
- 查看触发器是指查看数据库中已经存在的触发器的定义、状态和语法信息等。MySQL 中查看触发器的方法包括 SHOW TRIGGERS 语句和查询 information_schema 数据库下的 triggers 数据表等。
7.3.1 SHOW TRIGGERS 语句查看所有触发器
- SHOW TRIGGERS 语句用来查看当前创建的所有触发器的信息。因为该语句无法查询指定的触发器,所以在触发器较少的情况下,使用该语句会很方便。
基本语法
SHOW TRIGGERS;
- SHOW TRIGGERS; 语句 显示的信息描述,以上节我们创建的更新触发器 trigupdate 为例。
信息 | 描述 | 举例 |
---|---|---|
Trigger | 表示触发器的名称 | 在这里触发器的名称为 trigupdate; |
Event | 表示激活触发器的事件 | 这里的触发事件为更新操作 UPDATE; |
Table | 表示激活触发器的操作对象表 | 这里为 account 表; |
Statement | 表示触发器执行的操作 | 这里是向 myevent 数据表中插入一条数据; |
Timing | 表示触发器触发的时间 | 这里为更新操作之后(AFTER); |
一些其他信息,比如触发器的创建时间、SQL 的模式、触发器的定义账户和字符集等 | 这里不再一一介绍。 |
- 实例
使用 SHOW TRIGGERS 语句查看触发器(在 SHOW TRIGGERS 命令后添加\G,这样显示信息会比较有条理)。
可以看到,这里显示了我们在上节创建的三个触发器。
SHOW TRIGGERS \G
7.3.2 在triggers表中查看指定/全部触发器
- 在 MySQL 中,所有触发器的信息都存在 information_schema 数据库的 triggers 表中。如果要 查看特定触发器的信息或者数据库中触发器较多时,可以直接从 information_schema 数据库中的 triggers 数据表中查找。
- 基本语法1:查看指定触发器。
- '触发器名’用来指定要查看的触发器的名称,需要用单引号引起来。这种方式可以查询指定的触发器,使用起来更加方便、灵活。
SELECT * FROM information_schema.triggers WHERE trigger_name= '触发器名';
- 基本语法2:不指定触发器名称,查看所有的触发器。
SELECT * FROM information_schema.triggers \G
- SHOW TRIGGERS; 语句 显示的信息描述
信息 | 描述 |
---|---|
TRIGGER_SCHEMA | 表示触发器所在的数据库; |
TRIGGER_NAME | 表示触发器的名称; |
EVENT_OBJECT_TABLE | 表示在哪个数据表上触发; |
ACTION_STATEMENT | 表示触发器触发的时候执行的具体操作; |
ACTION_ORIENTATION | 值为 ROW表示在每条记录上都触发; |
ACTION_TIMING | 表示触发的时刻是 AFTER; |
一些其他信息,比如触发器的创建时间、SQL 的模式、触发器的定义账户和字符集等 | 这里不再一一介绍。 |
- 实例
使用 SELECT 命令查看 trigupdate 触发器。
SELECT * FROM information_schema.triggers WHERE TRIGGER_NAME= 'trigupdate'\G
7.4 修改和删除触发器
- 触发器不能更新或覆盖,为了修改一个触发器,必须删除原触发器,再以相同的名称创建新的触发器。
- 删除一个表的同时,也会自动删除该表上的触发器。
- 基本语法
- 触发器名
要删除的触发器名称。 - 数据库名
可选项。指定触发器所在的数据库的名称;若没有指定,则为当前默认的数据库。 - 权限
执行 DROP TRIGGER 语句需要 SUPER 权限。 - IF EXISTS
可选项。避免在没有触发器的情况下删除触发器。
- 触发器名
DROP TRIGGER [ IF EXISTS ] [数据库名] <触发器名>
- 实例
- 删除 double_salary 触发器。
DROP TRIGGER double_salary;
- 使用 SELECT 命令查看 trigupdate 触发器。
可以看到,结果为空 Empty set ,触发器已被删除。
SELECT * FROM information_schema.triggers WHERE TRIGGER_NAME= 'double_salary'\G
- 删除 double_salary 触发器。