MySQL 触发器的使用简介【转发】

本想总结一下关于MySQL触发器的使用,但是之前某大神给我分享了两篇很不错的触发器学习文章,遗憾的是没有原文的链接地址,于是只能这样恬不知耻的转发到自己的博客,用于今后的学习以及分享出来。总计两篇,拿走不谢~

第一篇 《触发器入门》

MySQL 5.1包含对触发器的支持。触发器是一种与表操作有关的数据库对象,当触发器所在表上出现指定事件时,将调用该对象,即表的操作事件触发表上的触发器的执行。

【创建触发器】

在MySQL中,创建触发器语法如下:
CREATE TRIGGER trigger_name trigger_time trigger_event
ON tbl_name FOR EACH ROW trigger_stmt

其中:
trigger_name:标识触发器名称,用户自行指定;
trigger_time:标识触发时机,取值为 BEFORE 或 AFTER;
trigger_event:标识触发事件,取值为 INSERT、UPDATE 或 DELETE;
tbl_name:标识建立触发器的表名,即在哪张表上建立触发器;
trigger_stmt:触发器程序体,可以是一句SQL语句,或者用 BEGIN 和 END 包含的多条语句。

由此可见,可以建立6种触发器,即:BEFORE INSERT、BEFORE UPDATE、BEFORE DELETE、AFTER INSERT、AFTER UPDATE、AFTER DELETE。
另外有一个限制是不能同时在一个表上建立2个相同类型的触发器,因此在一个表上最多建立6个触发器。

【trigger_event 详解】

MySQL 除了对 INSERT、UPDATE、DELETE 基本操作进行定义外,还定义了 LOAD DATA 和 REPLACE 语句,这两种语句也能引起上述6中类型的触发器的触发。
LOAD DATA 语句用于将一个文件装入到一个数据表中,相当与一系列的 INSERT 操作。
REPLACE 语句一般来说和 INSERT 语句很像,只是在表中有 primary key 或 unique 索引时,如果插入的数据和原来 primary key 或 unique 索引一致时,会先删除原来的数据,然后增加一条新数据,也就是说,一条 REPLACE 语句有时候等价于一条 INSERT 语句,有时候等价于一条 DELETE 语句加上一条 INSERT 语句。
因此:
INSERT 型触发器:插入某一行时激活触发器,可能通过 INSERT、LOAD DATA、REPLACE 语句触发;
UPDATE 型触发器:更改某一行时激活触发器,可能通过 UPDATE 语句触发;
DELETE 型触发器:删除某一行时激活触发器,可能通过 DELETE、REPLACE 语句触发。

【BEGIN … END 详解】

在MySQL中,BEGIN … END 语句的语法为:
BEGIN
[statement_list]
END
其中,statement_list 代表一个或多个语句的列表,列表内的每条语句都必须用分号(;)来结尾。
而在MySQL中,分号是语句结束的标识符,遇到分号表示该段语句已经结束,MySQL可以开始执行了。因此,解释器遇到 statement_list 中的分号后就开始执行,然后会报出错误,因为没有找到和 BEGIN 匹配的 END。这时就会用到 DELIMITER 命令(DELIMITER 是定界符,分隔符的意思),它是一条命令,不需要语句结束标识,语法为:
DELIMITER new_delemiter
new_delemiter 可以设为1个或多个长度的符号,默认的是分号(;),我们可以把它修改为其他符号,如管道符:
DELIMITER |
在这之后的语句,以分号结束,解释器不会有什么反应,只有遇到了管道符,才认为是语句结束。注意,使用完之后,我们还应该记得把它给修改回来。

【一个完整的创建触发器示例】

假设系统中有两个表:
班级表 class(班级号 classID, 班内学生数 stuCount)
学生表 student(学号 stuID, 所属班级号 classID)
要创建触发器来使班级表中的班内学生数随着学生的添加自动更新,代码如下:
DELIMITER |
create trigger tri_stuInsert after insert
on student for each row
begin
declare c int;
set c = (select stuCount from class where classID=new.classID);
update class set stuCount = c + 1 where classID = new.classID;
end|
DELIMITER ;

【变量详解】
MySQL 中使用 DECLARE 来定义一局部变量,该变量只能在 BEGIN … END 复合语句中使用,并且应该定义在复合语句的开头,

即其它语句之前,语法如下:
DECLARE var_name[,…] type [DEFAULT value]
其中:
var_name 为变量名称,同 SQL 语句一样,变量名不区分大小写;
type 为 MySQL 支持的任何数据类型;
可以同时定义多个同类型的变量,用逗号隔开;
变量初始值为 NULL,如果需要,可以使用 DEFAULT 子句提供默认值,值可以被指定为一个表达式。

对变量赋值采用 SET 语句,语法为:
SET var_name = expr [,var_name = expr] …

【NEW 与 OLD 详解】

上述示例中使用了NEW关键字,和 MS SQL Server 中的 INSERTED 和 DELETED 类似,MySQL 中定义了 NEW 和 OLD,用来表示

触发器的所在表中,触发了触发器的那一行数据。
具体地:
在 INSERT 型触发器中,NEW 用来表示将要(BEFORE)或已经(AFTER)插入的新数据;
在 UPDATE 型触发器中,OLD 用来表示将要或已经被修改的原数据,NEW 用来表示将要或已经修改为的新数据;
在 DELETE 型触发器中,OLD 用来表示将要或已经被删除的原数据;
使用方法: NEW.columnName (columnName 为相应数据表某一列名)
另外,OLD 是只读的,而 NEW 则可以在触发器中使用 SET 赋值,这样不会再次触发触发器,造成循环调用(如每插入一个学生前,都在其学号前加“2013”)。

【查看触发器】

和查看数据库(show databases;)查看表格(show tables;)一样,查看触发器的语法如下:
SHOW TRIGGERS [FROM schema_name];

其中,schema_name 即 Schema 的名称,在 MySQL 中 Schema 和 Database 是一样的,也就是说,可以指定数据库名,这样就不必先“USE database_name;”了。

【删除触发器】

和删除数据库、删除表格一样,删除触发器的语法如下:
DROP TRIGGER [IF EXISTS] [schema_name.]trigger_name

【触发器的执行顺序】

我们建立的数据库一般都是 InnoDB 数据库,其上建立的表是事务性表,也就是事务安全的。这时,若SQL语句或触发器执行失败,MySQL 会回滚事务,有:
①如果 BEFORE 触发器执行失败,SQL 无法正确执行
②SQL 执行失败时,AFTER 型触发器不会触发
③AFTER 类型的触发器执行失败,SQL 会回滚


第二篇 《MySQL-触发器的使用和创建》

什么是触发器

触发器(TRIGGER)是MySQL的数据库对象之一,从5.0.2版本开始支持。该对象与编程语言中的函数非常类似,都需要声明、执行等。但是触发器的执行不是由程序调用,也不是由手工启动,而是由事件来触发、激活从而实现执行。有点类似DOM中的事件。
那么为什么要使用数据库对象触发器呢?在具体开发项目时,经常会遇到如下实例:

<1> 在学生表中拥有字段学生姓名,字段学生总数,每当添加一条学生信息时,学生的总数就必须同时更改。

<2> 在学生表中还会有学生姓名的缩写,学生住址等字段,添加学生信息时,往往需要检查电话、邮箱等格式是否正确。
上面的例子使用触发器完成时具有这样的特点,需要在表发生改变时,自动进行一些处理。MySQL在触发DELETE/UPDATE/INSERT语句时就会自动执行所设置的操作,其他SQL语句则不会激活触发器。

创建触发器

使用帮助命令查看具体的语法:
CREATE
[DEFINER = { user | CURRENT_USER }]
TRIGGER trigger_name
trigger_time trigger_event
ON tbl_name FOR EACH ROW
trigger_body
语法中
trigger_name:触发器的名称,不能与已经存在的触发器重复;
trigger_time:{ BEFORE | AFTER },表示在事件之前或之后触发;
trigger_event::{ INSERT |UPDATE | DELETE },触发该触发器的具体事件;
tbl_name:该触发器作用在tbl_name上;

创建简单触发器

示例1,创建简单触发器

<1> 准备学生表和学生数目统计表

1
2
3
4
5
6
7
8
9
CREATE TABLE student_info (
stu_no INT(11) NOT NULL AUTO_INCREMENT,
stu_name VARCHAR(255) DEFAULT NULL,
PRIMARY KEY (stu_no)
);
CREATE TABLE student_count (
student_count INT(11) DEFAULT 0
);
INSERT INTO student_count VALUES(0);

<2> 创建简单触发器,在向学生表INSERT数据时,学生数增加,DELETE学生时,学生数减少

1
2
3
4
5
6
7
8
CREATE TRIGGER trigger_student_count_insert
AFTER INSERT
ON student_info FOR EACH ROW
UPDATE student_count SET student_count=student_count+1;
CREATE TRIGGER trigger_student_count_delete
AFTER DELETE
ON student_info FOR EACH ROW
UPDATE student_count SET student_count=student_count-1;

<3> INSERT、DELETE数据,查看触发器是否正常工作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
mysql> INSERT INTO student_info VALUES(NULL,'张明'),(NULL,'李明'),(NULL,'王明');
Query OK, 3 rows affected (0.02 sec)
Records: 3 Duplicates: 0 Warnings: 0

mysql> SELECT * FROM student_info;
+--------+----------+
| stu_no | stu_name |
+--------+----------+
| 1 | 张明 |
| 2 | 李明 |
| 3 | 王明 |
+--------+----------+
3 rows in set (0.00 sec)

mysql> SELECT * FROM student_count;
+---------------+
| student_count |
+---------------+
| 3 |
+---------------+
1 row in set (0.00 sec)

mysql> DELETE FROM student_info WHERE stu_name IN('张明','李明');
Query OK, 2 rows affected (0.00 sec)

mysql> SELECT * FROM student_info;
+--------+----------+
| stu_no | stu_name |
+--------+----------+
| 3 | 王明 |
+--------+----------+
1 row in set (0.00 sec)

mysql> SELECT * FROM student_count;
+---------------+
| student_count |
+---------------+
| 1 |
+---------------+
1 row in set (0.00 sec)

可以看到无论是INSERT还是DELETE学生,学生数目都是跟随着变化的。

创建包含多条执行语句的触发器

在trigger_body中可以执行多条SQL语句,此时的trigger_body需要使用BEGIN和END作为开始和结束的标志:

1
2
3
4
5
6
7
8
CREATE
[DEFINER = { user | CURRENT_USER }]
TRIGGER trigger_name
trigger_time trigger_event
ON tbl_name FOR EACH ROW
BEGIN
trigger_statement
END;

示例2,创建包含多条执行语句的触发器
依然沿用上面的例子中的表,对student_count表做如下变更:增加student_class字段表示具体年级的学生数,其中0表示全年级,1代表1年级……;同样学生表中也增加该字段。清空两个表中的所有数据。

<1> 删除上例中的两个触发器,初始化student_count表中数据,插入三条数据(0,0),(1,0),(2,0)表示全年级、一年级、二年级的初始人数都是0;

<2> 创建触发器,在INSERT时首先增加学生总人数,然后判断新增的学生是几年级的,再增加对应年级的学生总数:

1
2
3
4
5
6
7
8
9
10
DELIMITER $$
CREATE TRIGGER trigger_student_count_insert
AFTER INSERT
ON student_info FOR EACH ROW
BEGIN
UPDATE student_count SET student_count=student_count+1 WHERE student_class=0;
UPDATE student_count SET student_count=student_count+1 WHERE student_class= NEW.student_class;
END
$$
DELIMITER ;

<3> 创建触发器,在DELETE时首先减少学生总人数,然后判断删除的学生是几年级的,再减少对应年级的学生总数:

1
2
3
4
5
6
7
8
9
10
DELIMITER $$
CREATE TRIGGER trigger_student_count_delete
AFTER DELETE
ON student_info FOR EACH ROW
BEGIN
UPDATE student_count SET student_count=student_count-1 WHERE student_class=0;
UPDATE student_count SET student_count=student_count-1 WHERE student_class= OLD.student_class;
END
$$
DELIMITER ;

<4> 向学生表中分别插入多条不同年级的学生信息,查看触发器是否起作用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
mysql> INSERT INTO student_info VALUES(NULL,'AAA',1),(NULL,'BBB',1),(NULL,'CCC',2),(NULL,'DDD',2),(NULL,'ABB',1),(NULL,'ACC',1);
Query OK, 6 rows affected (0.02 sec)
Records: 6 Duplicates: 0 Warnings: 0

mysql> SELECT * FROM student_info;
+--------+----------+---------------+
| stu_no | stu_name | student_class |
+--------+----------+---------------+
| 4 | AAA | 1 |
| 5 | BBB | 1 |
| 6 | CCC | 2 |
| 7 | DDD | 2 |
| 8 | ABB | 1 |
| 9 | ACC | 1 |
+--------+----------+---------------+
6 rows in set (0.00 sec)

mysql> SELECT * FROM student_count;
+---------------+---------------+
| student_count | student_class |
+---------------+---------------+
| 6 | 0 |
| 4 | 1 |
| 2 | 2 |
+---------------+---------------+
3 rows in set (0.00 sec)

可以看到,总共插入了6条数据,学生总数是6,1年级4个,2年级2个,trigger正确执行。

<5> 从学生表中分别删除多条不同年级的学生信息,查看触发器是否起作用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
mysql> DELETE FROM student_info WHERE stu_name LIKE 'A%';
Query OK, 3 rows affected (0.02 sec)

mysql> SELECT * FROM student_info;
+--------+----------+---------------+
| stu_no | stu_name | student_class |
+--------+----------+---------------+
| 5 | BBB | 1 |
| 6 | CCC | 2 |
| 7 | DDD | 2 |
+--------+----------+---------------+
3 rows in set (0.00 sec)

mysql> SELECT * FROM student_count;
+---------------+---------------+
| student_count | student_class |
+---------------+---------------+
| 3 | 0 |
| 1 | 1 |
| 2 | 2 |
+---------------+---------------+
3 rows in set (0.00 sec)

从学生表中将姓名以A开头的学生信息删除,学生信息删除的同时,数量表也跟随变化。
在上面的示例中,使用了三个新的关键字:DELIMITER、NEW、OLD,这三个关键字在官网上“触发器语法”一节中都有介绍,整理如下:

DELIMITER

使用BEGIN…END结构,可以定义一个执行多句SQL的触发器。在BEGIN语句块中,还可以使用其它的语法,例如条件语句和循环语句。在MySQL中,分号”;”标志着SQL语句的结束,但是在触发器要执行的SQL语句中使用到了”;”作为要执行SQL语句的结束标记,所以你需要重新定义结束标识符。
重新定义结束标识符使用DELIMITER关键字,后面跟空格和重新定义的结束标识符。
注意:该语句与其他语句不同的是不需要在语句末尾添加结束标志符,如DELIMITER
的作用是将现有的结束标识符重新定义为
,但是,此时由于习惯或是疏忽在末尾添加了”;”也就是” DELIMITER
;”那么该语句的作用就变成了将符号”
;”作为新的结束标志符。

NEW和OLD

NEW在触发器为INSERT事件类型时有效,表示当前正在插入的数据;同理,OLD在触发器类型为DELETE事件类型时有效,表示当前正在删除的数据。
如上面的示例中,可以在触发器中使用NEW.student_class取得正在插入的学生信息中年级值,使用OLD.student_class取得正在删除的学生信息中的年级值。

触发器的使用限制

官网“触发器语法和示例” http://dev.mysql.com/doc/refman/5.6/en/trigger-syntax.html

<1> 触发器只能创建在永久表上,不能对临时表创建触发器;

<2> 触发器不能使用CALL语句调用具有返回值或使用了动态SQL的存储过程(存储过程可以使用OUT或INOUT参数返回给触发器返回值)。

<3> 触发器中不能使用开启或结束事务的语句段,比如,开始事务(START TRANSACTION)、提交事务(COMMIT)或是回滚事务(ROLLBACK),但是回滚到一个保存点(SAVEPOINT是允许的,因为回滚到保存点不会结束事务);

<4> 外键不会激活触发器;

<5> 当使用基于行的复制时,从表上的触发器不会因操作主表中的数据而激活。当使用基于语句的复制时,从表上的触发器会被激活。参考 Section 17.4.1.34,“Replication and Triggers”;

<6> 触发器中不允许返回值,因此触发器中不能有返回语句,如果要立即停止一个触发器,应该使用LEAVE语句;

触发器中的异常机制

MySQL的触发器是按照BEFORE触发器、行操作、AFTER触发器的顺序执行的,其中任何一步发生错误都不会继续执行剩下的操作。如果是对事务表进行的操作,那么会整个作为一个事务被回滚,但是如果是对非事务表进行的操作,那么已经更新的记录将无法回滚,这也是设计触发器的时候需要注意的问题。
查看触发器

可以通过执行SHOW TRIGGERS命令查看触发器,但是因为不能查询指定的触发器,所以每次都返回所有的触发器的信息,使用不方便。但是可以使用查询系统表information_schema.triggers的方式指定查询条件,查看指定的触发器信息。如:

1
2
3
mysql> USE information_schema;
Database changed
mysql> SELECT * FROM triggers WHERE trigger_name='trigger_student_count_insert';

删除触发器

DROP TRIGGER trigger_name;