MySQL中的存储过程和触发器详解
作者:BUG召唤师
本文系统介绍了MySQL存储过程的创建与使用,包括变量定义、条件判断、循环控制、游标操作等核心知识。
详细讲解了存储过程的优缺点分析,以及存储函数与触发器的区别与应用场景。
通过具体示例演示了如何实现参数传递、条件处理、日志记录等功能,并深入解析了行级触发器的实现机制。
文章还总结了存储过程在实际开发中的常见问题与解决方案,为数据库编程提供了全面指导。
一、存储过程
1. 存储过程介绍
MySQL 的存储过程是为了完成特定功能的一组 sql 语句集,用户通过存储过程的名字和参数进行调用,类似于 java 中的方法。
- 将业务逻辑封装在了数据库内部,减少了应用程序的复杂性;
- 集中管理数据库的操作,便于维护和更新;
- 可以被多次调用,代码重用性高;
2. 优缺点分析
存储过程的优点:
- 1. 存储过程在创建时,编译并保存在数据库中,执行速度比单个的 sql 要快;
- 2. 存储过程可以重复调用,减少重复代码,提高代码的可维护性;
- 3. 可以限制用于直接访问数据库,通过存储过程间接访问,从而保证系统的安全性;
- 4. 可以在存储过程中实现复杂的事务逻辑;
- 5. 当表结构发生变化,只需要响应修改存储过程,应用程序只需要关注存储过程的接口,代码改动较小,因此存储过程降低了代码的耦合性;
存储过程的缺点:
- 1. 存储过程的可移植性差,更换数据库后,需要重新编写存储过程;
- 2. 只有少数数据库支持存储过程的代码调试,因此维护困难;
- 3. 数据库的主要功能是保存和查询数据,数据是保存在硬盘中,存储和查询数据都涉及到硬盘 IO,效率不高,数据库常常称为系统的效率瓶颈。对于高并发场景,将业务逻辑放在数据库层面执行,会增加数据库的压力,进一步导致效率降低。
3. 存储过程的语法
delimiter 关键字用于定义语句的结束标识符,例如 "delimiter //" 就是将结束标识符定义为 "//";
-- 修改 sql 语句结束标识符为 // delimiter // -- 创建存储过程 create procedure 存储过程名(参数, 参数...) begin select 列名, 列名... from exam; end// -- 修改 sql 语句结束标识符为 ; delimiter; -- 查看 topoc01 中的所有存储过程 select * from information_schema.routines where routine_schema = 数据库名; -- 查看存储过程的定义 show create procedure 存储过程名; -- 调用存储过程 call 存储过程名(参数, 参数...); -- 删除存储过程 drop procedure if exists 存储过程名;
注意:
- 创建存储过程中,begin 后面的 sql 语句,是需要写分号的;
- 如果不使用 delimiter 改变语句的结束标识符,在命令行中遇到分号就会认为语句结束了,存储过程就会创建失败;
- 改变语句的结束标识符为 "//" 后,命令行中遇到 "//" 后才会认为语句结束,因此能够创建成功;
- 创建成功后,记得将结束表示符修改为分号,避免后续写其他 sql 时报错;
二、变量
1. 系统变量
系统变量分为全局变量和会话变量;
查询系统变量:
-- 查看系统变量 show global variables; -- 查看会话变量 show session variables; -- 搭配模糊查询 - 全局变量 show global variables like 'xxx%'; -- 搭配模糊查询 - 会话变量 show session variables like '%xxx%'; -- 使用 select 进行精确查询 - 全局变量 select @@global.系统变量名; -- 使用 select 进行精确查询 - 会话变量 select @@session.系统变量名;
注意:
- 如果不指定时全局变量或者会话变量,默认是会话变量;
设置系统变量:
-- 设置系统变量 - 全局变量 set global 系统变量名 = 值; -- 设置系统变量 - 会话变量 set session 系统变量名 = 值; -- 设置系统变量 - 全局变量 set @@global.全局变量名 = 值; -- 设置系统变量 - 会话变量 set @@session.会话变量名 = 值;
注意:
- 如果没有指定 global 或者 session,修改的默认为会话变量;
- 会话关闭后,设置的会话变量失效;新建的会话默认读全局系统变量的值为初始值;
- MySQL 服务器重启后,设置的全局变量失效;如果想使系统变量永久生效,需要修改配置文件;
2. 用户自定义变量
用户自定义变量是 sql 会话中定义的变量,不用提前申明,作用域为当前会话;
-- 申明 + 赋值 set @var_name := 表达式; select @var_name := 表达式; select 列名 into @var_name from 表名; -- 使用自定义变量 select @var_name;
3. 局部变量
局部变量只在存储过程,函数或者触发器的范围内有效;
需要使用关键字 declare 申明,作用域为 begin 和 end 中间的代码块内;
-- 局部变量申明的语法 declare 局部变量名 类型 default 默认值; -- 局部变量在存储过程中的应用 delimiter// -- 定义存储过程 create procedure p1() begin -- 定义局部变量 declare stu_count int default 0; -- 给局部变量赋值 select count(*) into stu_count from student; -- 使用局部变量 select stu_count; end// delimiter ;
三、SQL 编程
1. 条件判断语句
-- 条件判断语句 if 条件1 then 具体执行逻辑; elseif 条件2 then 具体执行逻辑; ... else 具体执行逻辑; end if;
- 示例:
-- 练习 -- 分数>=90分等级为优秀 -- 分数>=80且分数<90分等级为良好 -- 分数>=60分且分数<80分等级为及格 -- 分数<60分等级为不及格 delimiter // create procedure p1() begin declare score int default 86; declare result varchar(20); if score >= 90 then set result := '优秀'; elseif score >=80 then set result := '良好'; elseif score >= 60 then set result := '及格'; else set result := '不及格'; end if; select result; end// delimiter ; call p1();
2. 参数
参数类型有三种:in(输入型),out(输出型),inout(输入输出型);
- 示例:
-- 练习 -- 传⼊⼀个分数的值,判定当前分数对应的等级 -- 分数>=90分等级为优秀 -- 分数>=80且分数<90分等级为良好 -- 分数>=60分且分数<80分等级为及格 -- 分数<60分等级为不及格 delimiter // create procedure p2(in score int, out result varchar(20)) begin if score >= 90 then set result := '优秀'; elseif score >=80 then set result := '良好'; elseif score >= 60 then set result := '及格'; else set result := '不及格'; end if; end// delimiter ; -- 调用 call p2(86, @result); select @result; -- 传⼊⼀个分数的值,在传⼊分数的基础上加10分,然后返回 delimiter // create procedure p3(inout score int) begin set score = score + 10; end// delimiter ; set @score := 50; call p3(@score); select @score;
3. case 判断语句
-- 语法⼀ CASE case_value WHEN when_value THEN statement_list [WHEN when_value THEN statement_list] ... [ELSE statement_list] END CASE -- 语法⼆ statement_list (如果存在 CASE WHEN search_condition THEN statement_list [WHEN search_condition THEN statement_list] ... [ELSE statement_list] END CASE
- 示例:
-- ⽰例⼀:传⼊⼀个状态码,输出该状态码表⽰的含义 -- 0:成功 -- 10001:⽤⼾名或密码错误 -- 10002:您没有对应的权限,请联系管理员 -- 20001:你传⼊的参数有误 -- 20002:没有找到相应的结果 delimiter // create procedure p4(in code int, out result varchar(20)) begin case code when 0 then set result := '成功'; when 10001 then set result := '用户名或密码错误'; when 10002 then set result := '您没有对应的权限,请联系管理员'; when 20001 then set result := '你传⼊的参数有误'; when 20002 then set result := '没有找到相应的结果'; else set result = '服务器错误,请联系管理员'; end case; end// delimiter ; call p4(10001, @result); select @result; -- ⽰例⼆:根据传⼊的⽉份,输出该⽉份属于哪个季度 -- 1~3⽉为第⼀季度 -- 4~6⽉为第二季度 -- 7~9⽉为第三季度 -- 1~12⽉为第四季度 delimiter // create procedure p5(in month int, out result varchar(20)) begin case when month >= 1 and month <= 3 then set result := '第⼀季度'; when month >= 4 and month <= 6 then set result := '第二季度'; when month >= 7 and month <= 9 then set result := '第三季度'; when month >= 10 and month <= 12 then set result := '第四季度'; else set result := '非法参数'; end case; end// delimiter ; call p5(6, @result); select @result;
4. 循环
while 循环:
WHILE search_condition Do statement_list END WHILE;
- 示例:
-- 传⼊⼀个数n,计算从1累加到n的值 delimiter // create procedure p6(in n int, out result int) begin declare num int default 1; set result := 0; while num <= n do set result := result + num; set num := num + 1; end while; end// delimiter ; call p6(3, @result); select @result;
repeat 循环:
REPEAT statement_list UNTIL search_condition END REPEAT;
- 示例:
delimiter // create procedure p7(in n int, out sum int) begin set sum := 0; repeat set sum := sum + n; set n := n - 1; until n = 0 end repeat; end// delimiter ; call p7(5, @sum); select @sum;
loop 循环:
[begin_label:] LOOP statement_list END LOOP [end_label]
- 示例:
-- 传⼊⼀个数n,累加从1累加到n之间偶数的值 delimiter // create procedure p8(in n int) begin declare sum int default 0; sum_label: LOOP IF n <= 0 THEN LEAVE sum_label; END IF; set sum := sum + n; set n := n - 1; END LOOP sum_label; select sum; end// delimiter ; call p8(100); -- 传⼊⼀个数n,累加从1累加到n之间偶数的值 delimiter // create procedure p9(in n int) BEGIN declare sum int default 0; sum_label: LOOP IF n <= 0 THEN LEAVE sum_label; END IF; IF n % 2 != 0 then set n := n - 1; ITERATE sum_label; END IF; set sum := sum + n; set n := n - 1; END LOOP sum_label; select sum; END// delimiter ; call p9(10);
5. 游标和条件处理程序
游标是一种数据库对象,允许在存储过程和函数中对查询到的结果进行逐行检索。
- 使用游标之前,必须声明游标,之后使用 open,fetch,close 语句来开启游标,获取游标记录和关闭游标;
- 游标必须在条件处理程序之前被声明,并且变量必须要在游标和条件处理程序之前被声明;
- 通过查询语句获取的记录通常是多行,因此游标通常会和循环搭配使用;
- 打开游标的时候,才会真正执行 sql 查询语句;
-- 声明游标 declare 游标名 cursor for 查询语句; -- 打开游标 open 游标名; -- 获取游标记录 fetch 游标名 into 变量名, 变量名...; -- 关闭游标 close 游标名;
- 条件处理程序的条件是程序执行过程中可能遇到的问题,类似 java 中的异常;
- 处理程序是遇到问题应该采取的处理方式;
- 使用条件处理程序的作用是保证存储过程或者函数在遇到错误时能继续执行,可以增强程序处理问题的能力,避免程序异常停止运行;
-- 声明条件处理程序 declare handler_action handler for condition_value, condition_value... statement; handler_action:{ continue, - 继续执行当前程序 exit - 退出当前程序 } condition_value:{ mysql_error_code -- MYSQL错误码 | SQLSTATE [VALUE] sqlstate_value -- 状态码 | SQLWARNING -- 所有以 01开头的 SQLSTATE 代码 | NOT FOUND -- 所有以 02开头的 SQLSTATE 代码 | SQLEXCEPTION -- 所有没有被 SQLWARNING 或 NOT FOUND 捕获的 SQLSTATE 代码 }
- 示例:
-- 示例:传入班级编号,查询学生表中属于该班级的学生信息 -- 并将符合条件的学生信息写入到⼀张新表中 -- 新表及字段 t_student_class(id,student_name,class_name) -- 实现逻辑 -- 定义变量⽤于接收查询结果集中每⼀⾏中列的值 -- 声明游标,⽤于接收查询结果集 -- 创建新表 -- 开启游标 -- 从游标中获取结果集中的记录 -- 向新表中写⼊数据 -- 关闭游标 delimiter // create procedure p10(in class_id int) begin -- 定义变量⽤于接收查询结果集中每⼀⾏中列的值 declare student_name varchar(20); declare class_name varchar(20); -- 声明游标,⽤于接收查询结果集 declare s_cursor cursor for select student.name, class.name from student, class where student.class_id = class.id and class.id = class_id; -- 创建新表 create table if not exists t_student_class ( id int primary key auto_increment, student_name varchar(20) not null, class_name varchar(20) not null ); -- 开启游标 open s_cursor; -- 从游标中获取结果集中的记录 while true do fetch s_cursor into student_name, class_name; -- 向新表中写⼊数据 insert into t_student_class values (student_name, class_name); -- 关闭游标 close s_cursor; end// delimiter ;
注意:
- 因为查询语句获取的数据是有限的,因此当使用游标遍历到最后一行的下一行后,应当退出循环,因此代码需要改进,做法是使用条件处理程序退出;
delimiter // create procedure p10(in class_id int) begin -- 定义变量⽤于接收查询结果集中每⼀⾏中列的值 declare student_name varchar(20); declare class_name varchar(20); -- 定义循环退出条件 declare is_done bool default false; -- 声明游标,⽤于接收查询结果集 declare s_cursor cursor for select student.name, class.name from student, class where student.class_id = class.id and class.id = class_id; -- 声明条件处理程序 declare continue handler for not found set is_done := true; -- 创建新表 create table if not exists t_student_class ( id int primary key auto_increment, student_name varchar(20) not null, class_name varchar(20) not null ); -- 开启游标 open s_cursor; -- 从游标中获取结果集中的记录 while not is_done do fetch s_cursor into student_name, class_name; -- 向新表中写⼊数据 insert into t_student_class values (null, student_name, class_name); end while; -- 关闭游标 close s_cursor; end// delimiter ;
注意,上述程序的条件处理程序的问题是:
- 当游标(fetch 语句)读取到最后一行的下一行时,程序发生错误;
- 条件处理程序将上述的 is_done 变量置为 true 后,又继续执行 while 循环 fetch 后面的语句;
- 上面的 student_name 和 class_name 继续和上次保持一致,因此又多插入了一行上次的数据;
- 因此我们必须要在 t_student_class 插入数据之前,退出循环。
while 循环没有退出方式,因此 while 循环并不适合这里的需求,而 loop 循环是能够满足需求的方式,因此使用 loop 循环代替 while 循环,将代码更改为:
delimiter // create procedure p10(in class_id int) begin -- 定义变量⽤于接收查询结果集中每⼀⾏中列的值 declare student_name varchar(20); declare class_name varchar(20); -- 定义循环退出条件 declare is_done bool default false; -- 声明游标,⽤于接收查询结果集 declare s_cursor cursor for select student.name, class.name from student, class where student.class_id = class.id and class.id = class_id; -- 声明条件处理程序 declare continue handler for not found set is_done := true; -- 创建新表 create table t_student_class(id,student_name,class_name); -- 开启游标 open s_cursor; -- 从游标中获取结果集中的记录 read_loop: loop fetch s_cursor into student_name, class_name; if is_done then leave read_loop; end if; insert into t_student_class values (null, student_name, class_name); end loop read_loop; -- 关闭游标 close s_cursor; end// delimiter ;
6. 存储函数
MySQL 的存储函数是返回值的存储过程,参数只能是 in 类型,因此 in 也可以省略;
存储函数和存储过程的主要区别是:存储函数必须有返回值,存储过程没有返回值,只能用输出型或者输入输出型参数作为输出的结果。
-- 定义存储函数 delimiter // create function 存储函数名(参数列表) returns 类型 characterristic begin SQL 语句... return 返回值; end// delimiter ; characteristic: deterministic | not deterministic | no sql | reads sql data | modifies sql data -- 使用存储函数 select 存储函数名(参数列表);
示例:传入一个数 n,计算从 1 累加到 n 的值;
delimiter // create function func1(n int) returns int deterministic begin declare total int default 0; while n >= 1 do set total := total + n; set n := n - 1; end while; return total; end// delimiter ; select func1(100);
四、触发器
1. 触发器的定义和类型
触发器是一个与表相关联的数据库对象;
在对表进行 insert,update,delete 操作时,触发并执行触发器中定义的 sql 语句;
- 触发器可以在对表操作之前或者之后执行,这被称为触发时间;
- 触发器在对表操作之前执行,典型的应用场景是对 sql 语句参数进行校验;
- 触发器在对表操作之后执行,典型的应用场景是记录对表操作的日志;
MySQL 支持 3 中类型的触发器:insert 触发器, update 触发器,delete 触发器;
可以使用 OLD 和 NEW 关键字引用触发器中发生变化的数据被容:
- insert 触发器:只能使用 NEW 来引用新插入的数据内容;
- update 触发器:可以使用 OLD 引用修改前的数据内容,NEW 引用修改后的数据内容;
- delete 触发器:只能使用 OLD 引用删除前的数据内容;
触发器也可以分为行级触发器和语句级触发器:
- 行级触发器指的是对表中的每一行进行 insert,update,delete 操作时,行级触发器都会被触发;如果一个语句影响了多行数据,每一行数据都会触发一次行级触发器;
- 语句级触发器指的是对表中的数据进行 insert, update,delete 操作时,只触发一次。无论该操作影响了多少行数据,语句级触发器都只触发一次;
- MySQL 中只支持行级触发器,不支持语句级触发器;
2. 触发器的语法及实现
-- 定义触发器 delimiter // create trigger if not exists 触发器名 触发时间 触发类型 on 表名 for each row begin 触发器逻辑; end delimiter ; trigger time: before | after trigger event: insert / update / delete -- 查看触发器 show triggers; -- 删除触发器 drop trigger if exists 触发器名;
示例:通过触发器记录学生表的变更日志,将变更日志写入到表 student_log 中,包含增加,修改和删除操作。
1. 创建 student_log 表
-- 创建 student_log 表 create table student_log ( id bigint primary key auto_increment, operation_type varchar(10) not null comment '操作类型:insert, update, delete', operation_time datetime not null comment '操作时间', operation_id bigint not null comment '操作记录 id', operation_data varchar(500) not null comment '操作数据' );
2. 创建 isnert 触发器
-- 创建 insert 触发器 delimiter // create trigger tgr_student_insert after insert on student for each row begin insert into student_log ( operation_type, operation_time, operation_id, operation_data ) values ( 'insert', now(), new.id, concat(new.id, ',' new.name, ',' new.sno, ',' new.age, ',' new.gender, ',' new.enroll_date, ',' new.class_id ) ); end// delimiter ; -- 插入数据记录 insert into student values (null, '曹操', '300001', 28, 1, '2024-9-1', 3);
3. 创建 update 触发器
-- 创建 update 触发器 delimiter // create trigger tgr_student_update after update on student for each row begin insert into student_log ( operation_type, operation_date, operation_id, operation_data ) values ( 'update', now(), new.id, concat(old.id, ', ', old.name, ', ', old.sno, ', ', old.age, ', ', old.gender, ', ', old.enroll_date, ', ', old.class_id, ' | ', new.id, ', ', new.name, ', ', new.sno, ', ', new.age, ', ', new.gender, ', ', new.enroll_date, ', ', new.class_id) ); end// delimiter ; -- 更新学生表 update student set age = 20 where name = '曹操'; update student set class_id = 3 where id >= 7;
4. 创建 delete 触发器
-- 创建 delete 触发器 delimiter // create trigger tgr_student_delete after delete on student for each row begin insert into student_log ( operation_type, operation_time, operation_id, operation_data ) values ( 'delete', now(), old.id, concat(old.id, ', ', old.name, ', ',old.sno, ', ',old.age, ', ',old.gender, ', ',old.enroll_date, ', ',old.class_id) ); end// delimiter ; -- 删除数据记录 delete from student where name = '曹操';
五、总结
1. 存储过程的作用是什么?
存储过程的作用是通过一个 sql 语句集,在数据库层面实现特定的业务逻辑;类似于 java 中的方法。
优点是:
- 业务逻辑编译完成后可以保存在数据库中,实现一次编写,重复调用;
- 在数据表和应用程序之间增加一个存储过程,避免应用程序直接访问数据库,避免不安全的问题;
- 应用程序访问数据库只需要关心存储过程的名字和参数,数据表发生变化的时候只需要修改存储过程,不需要修改应用程序;
缺点是:
- MySQL 是一个客户端-服务器结构的程序,一个服务器给多个客户端提供服务。
- 如果并发量比较高的情况下,存储过程会给服务器带来较大的负担,降低服务器的效率;
2. 如何创建一个存储过程?
使用 create 关键字:create procedure 存储过程名字(参数列表);
begin 后面编写 sql 语句集,end 结束语句集的编写;
需要注意的点是:要使用 delimiter 来修改 sql 语句终止符,默认终止符是分号,避免在命令行中执行的时候,服务器认为语句中的分号为终止符,导致创建失败;
3. MySQL 中的变量有哪几种?
有三种,分别是:系统变量,用户自定义变量,局部变量;
系统变量又分为全局变量和会话变量,全局变量修改成功后对所有会话都生效,但是如果服务器重启,修改的效果就会失效;会话变量修改后只在当前会话生效,会话关闭后,就会失效;
用户自定义变量:不需要提前声明,用的时候直接写 @变量名 即可;
局部变量用于存储过程,存储函数,触发器中,需要提前使用关键字 declare 声明;
4. 如何定义一个变量?
系统变量是不可以自定义的;
用户自定义变量可以通过 set @变量名定义;
局部变量通过 declare 变量名 变量类型 默认值的方式定义;
5. MySQL中使用变量是否需要提前声明?
如果是用户自定义变量,不需要提前声明;
如果在存储过程,存储函数,触发器中使用局部变量,需要使用 declare 关键字提前申明;
6. MySQL中的参数分为哪几种?
MuSQL 中参数分为 3 种:in - 输入型参数, out - 输出型参数,inout - 输入输出型参数;
存储过程,存储函数都可以使用输入型参数,但是存储函数只能使用输入型参数,因此 in 可以省略;存储过程中可以使用输出型参数或者输入输出型参数代替返回值;
7. 用过游标吗?游标的作用是什么?
用过游标,游标的作用是遍历使用查询语句获得的结果集;游标默认指向的是查询结果集的前一行,使用 fetch 游标名 into 变量名的方式将结果集中的列保存到变量中,fetch 操作后,游标会向结果集的下一行移动;
因此游标通常会和循环搭配使用,由于游标走到结果集到最后一行会发生越界,sql 会报错,通常回合条件处理程序配合使用;
8. 了解条件处理程序吗?介绍⼀下如何使用?
条件处理程序需要注意两点:
第一点是条件,条件可以是 MySQL 的错误码,或者 SQL 语言的状态码;
第二点是处理程序:满足条件后才会执行处理程序,处理程序的逻辑有两种,continue 和 exit,满足条件时,continue 会继续让程序执行,增强程序处理问题的能力;exit 则会退出程序;
局部变量和游标必须要在条件处理程序之前先定义;
定义条件处理程序的方式是 declare handler_action hander condition_value,...;
9. 存储函数与存储过程的区别是什么?
存储函数必须要求有返回值,存储过程不需要有返回值;
存储函数的参数列表只能有 in 类型的参数,因此 in 也可以省略;
存储函数声明的时候必须要写 returns,后面跟上返回值类型,在 MySQL 8 中,开启 binlog 的前提条件下,返回值类型后面必须写 deterministic,或者 not deterministic,不写会报错;
- deterministic 的意思是给定一个固定的输入,输出总是相同的;
- not deterministic 的意思是给定一个固定的输入,有可能输出是不同的;
10. 如何查看数据库中创建的存储过程?
select * from information_schema.routines where routine_schema = '数据库名';
也可以:
show create procedure 存储过程名;
11. 什么是触发器?
触发器是一个与表相关联的数据库对象;
在进行 insert, update, delete 操作时,触发执行触发器定义的 sql 语句;
12. MySQL中触发器分为几种类型?
MySQL 中触发器分为三种类型:insert 触发器,update 触发器,delete 触发器;
触发器的触发时间分为 before 和 after;
- before 是在 sql 执行前触发,可以用于参数校验等;
- after 是在 sql 执行后触发,可以用于记录日志等;
13. 行级触发器与语句级触器的区别是什么?
- 行级触发器是在执行 insert, update, delete 时,受到影响的每一行数据都会触发一次;如果一个 sql 语句影响了多行数据,每一行数据都会触发一次触发器;
- 语句集触发器是在执行 insert, update, delete 时,只执行一次触发器,即使一个 sql 语句影响到了多行数据,也只会执行一次;
14. 说⼀下了解的触发器使用场景都有哪些?
根据触发时间分为 before 和 after;
- 如果 sql 语句执行前触发,这种通常用于参数校验,参数类型约束,检查传入的参数是否满足数据库的要求;
- 如果 sql 语句执行后触发,这种通常用于日志的记录;
15. 如果对⼀表中的数据进行更新,要在日志表中记录该条记录更新前与更新后的值,如何实现?
首先要创建日志表;
第二要定义一个 update 触发器,触发器时间设置为 after;
触发器的语句集中写 insert 语句,记录发生变化时的操作类型,操作时间,操作数据记录的 id,还有针对列的修改;、
16. 如何查看数据库中创建的触发器?
show triggers;
可以查看数据库中定义的所有触发器;
也可以
show create trigger 触发器名;
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。