RDBMS高级技术之触发器
本章学习目标
理解触发器
熟练掌握触发器操作
MySQL5.0.2版本开始支持触发器的功能。触发器是与表有关的数据库对象,在满足定义条件时触发,并执行触发器中定义的语句集合。触发器的这种特性可以协助应用在数据库端确保数据的完整性,本章将详细讲解MySQL的触发器。
10.1 触发器概述
10.1.1 触发器的概念及优点
前面章节学习了MySQL的存储过程,MySQL中还有一种类似的存在——触发器,它的执行不是由程序调用,也不是手动开启,而是由事件来触发。当对某个表操作时,会自动激活触发器并执行,例如对一个表进行操作(insert、delete、update)时会激活并执行触发器。
触发器是用户定义在关系表上的一类由事件触发的特殊过程。一旦定义,任何用户对表的增、删、改操作均由服务器自动激活相应的触发器。触发器类似于约束,但是比约束灵活,具有更强大的数据控制能力。
触发器的优点如下所示。
自动执行:触发器在操作表数据后立即被激活。
级联更新:触发器可以通过数据库中的相关表进行层叠更改。
强化约束:触发器可以引用其他表中的列,能够实现比CHECK约束更为复杂的约束。
跟踪变化:触发器可以阻止数据库中未经许可的指定更新和变化。
强制业务逻辑:触发器可用于执行管理任务,并强制影响数据库的复杂业务规则。
10.1.2 触发器的作用
触发器是基于行触发的,所以删除、新增或者修改操作可能都会激活触发器,所以不要编写过于复杂的触发器,也不要增加过多的触发器,这样会对数据的插入、修改或者删除带来比较严重的影响,同时也会带来可移植性差的后果,所以在设计触发器的时候一定要有所考虑。
触发器是一种特殊的存储过程,它在插入,删除或修改特定表中的数据时触发执行,它比数据库本身标准的功能有更精细和更复杂的数据控制能力。
触发器主要有六个作用,具体如下所示。
安全性:可以基于数据库使用户具有操作数据库的某种权利,可以基于时间限制用户的操作,例如不允许下班后和节假日修改数据库数据,还可以基于数据库中的数据限制用户的操作,例如不允许某个用户做修改操作。
审计:可以跟踪用户对数据库的操作,审计用户操作数据库的语句,把用户对数据库的更新写入审计表。
实现复杂的数据完整性规则:实现非标准的数据完整性检查和约束。触发器可产生比规则更为复杂的限制。与规则不同,触发器可以引用列或数据库对象,还可以提供可变的缺省值。
实现复杂的非标准的数据库相关完整性规则:触发器可以对数据库中相关的表进行连环更新。
同步实时地复制表中的数据。
自动计算数据值,如果数据的值达到了一定的要求,则进行特定的处理。
10.2 触发器操作
上节内容详细阐述了触发器的基本概念,接下来将讲解触发器的操作,包括创建触发器、查看触发器、使用触发器和删除触发器。
10.2.1 数据准备
在讲解触发器前,首先创建两张数据表,用于后面的例题演示,分别为测试表test1和测试表test2,首先来了解一下表结构,测试表test1的表结构如表10.1所示。
表10.1 test1表
字段 |
字段类型 |
说明 |
id |
int |
编号 |
name |
varchar(50) |
姓名 |
表10.1中列出了测试表test1的字段、字段类型和说明,接着创建测试表test1,SQL语句如下所示。
mysql> CREATE TABLE test1(
-> id INT,
-> name VARCHAR(50)
-> );
Query OK, 0 rows affected (0.16 sec)
接着创建测试表test2,表结构如表10.2所示。
表10.2 test2表
字段 |
字段类型 |
说明 |
id |
int |
编号 |
name |
varchar(50) |
姓名 |
表10.2中列出了测试表test2的字段、字段类型和说明,接着创建测试表test2,SQL语句如下所示。
mysql> CREATE TABLE test2(
-> id INT,
-> name VARCHAR(50)
-> );
Query OK, 0 rows affected (0.16 sec)
至此,两张表创建完成,本章后面的演示例题会用到这两张表。
10.2.2 创建触发器
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包含的多条语句。由此可以看出,一共可以创建六种触发器,即BEFORE INSERT、BEFORE UPDATE、BEFORE DELETE、AFTER INSERT、AFTER UPDATE和AFTER DELETE。另外还要注意,不能同时在一个表上建立两个相同类型的触发器,因此在一个表上最多建立六个触发器。
MySQL除了对INSERT、UPDATE、DELETE基本操作进行定义外,还定义了LOAD DATA和REPLACE语句,这两种语句也能引起上述六种类型触发器的触发。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语句触发。
学习了创建触发器语法格式后,接下来通过具体案例演示INSERT型触发器的创建和使用。
例10-1 创建触发器t_afterinsert_on_test1,用于向测试表test1添加记录后,自动将记录备份到测试表test2中,SQL语句如下所示。
mysql> delimiter //
mysql> CREATE TRIGGER t_afterinsert_on_test1
-> AFTER INSERT ON test1
-> FOR EACH ROW
-> BEGIN
-> INSERT INTO test2(id,name) values(NEW.id,NEW.name);
-> END//
Query OK, 0 rows affected (0.27 sec)
以上执行结果证明触发器创建完成,接下来测试触发器的使用,首先向测试表test1中插入一条数据,SQL语句如下所示。
mysql> delimiter ;
mysql> INSERT INTO test1(id,name) values(1,'zs');
Query OK, 1 row affected (0.24 sec)
以上执行结果证明数据插入完成,可以查看表中数据,SQL语句如下所示。
mysql> SELECT * FROM test1;
+------+------+
| id | name |
+------+------+
| 1 | zs |
+------+------+
1 row in set (0.03 sec)
以上执行结果可以看出数据插入完成,此时查看测试表test2中的数据,SQL语句如下所示。
mysql> SELECT * FROM test2;
+------+------+
| id | name |
+------+------+
| 1 | zs |
+------+------+
1 row in set (0.02 sec)
从以上执行结果可看出,测试表test2自动备份了向测试表test1中插入的数据,这是因为在进行INSERT操作时,激活了触发器t_afterinsert_on_test1,触发器自动向测试表test2中插入了同样的数据。
上述示例中使用了NEW关键字,和SQL Server中的INSERTED和DELETED类似,MySQL中定义了NEW和OLD,用来表示触发器的所在表中,触发了触发器的哪一行数据,NEW和OLD的具体用法如下所示。
在INSERT型触发器中,NEW用来表示将要(BEFORE)或已经(AFTER)插入的新数据。
在UPDATE型触发器中,OLD用来表示将要或已经被修改的原数据,NEW用来表示将要或已经修改为的新数据。
在DELETE型触发器中,OLD用来表示将要或已经被删除的原数据。
NEW关键字的使用语法格式如下所示。
NEW.columnName
以上语法格式中,columnName表示相应数据表的某个列名,OLD关键字也是类似的使用方法,另外需要注意的是,OLD是只读的,而NEW则可以在触发器中使用SET赋值,这样不会再次触发触发器,造成循环调用。
接下来再通过一个案例演示DELETE型触发器的使用。
例10-2 创建触发器t_afterdelete_on_test1,用于删除测试表test1记录后,自动将测试表test2中的对应记录删除,SQL语句如下所示。
mysql> delimiter //
mysql> CREATE TRIGGER t_afterdelete_on_test1
-> AFTER DELETE ON test1
-> FOR EACH ROW
-> BEGIN
-> DELETE FROM test2 WHERE id=OLD.id;
-> END//
Query OK, 0 rows affected (0.18 sec)
以上执行结果证明触发器创建完成,接下来测试触发器的使用,首先删除测试表test1中id为1的数据,SQL语句如下所示。
mysql> delimiter ;
mysql> DELETE FROM test1 WHERE id=1;
Query OK, 1 row affected (0.16 sec)
以上执行结果证明数据删除完成,可以查看表中数据,SQL语句如下所示。
mysql> SELECT * FROM test1;
Empty set (0.00 sec)
以上执行结果可以看出数据删除完成,此时查看测试表test2中的数据,SQL语句如下所示。
mysql> SELECT * FROM test2;
Empty set (0.00 sec)
从以上执行结果可看出,测试表2中的记录同样被删除,这是因为在进行DELETE操作时,激活了触发器t_afterdelete_on_test1,触发器自动删除了测试表test2中对应的记录。
10.2.3 查看触发器
查看触发器有两种方式,接下来针对两种方式分别讲解。
1.使用SHOW TRIGGERS语句查看触发器
查询触发器,可以使用SHOW TRIGGERS语句,具体语法格式如下。
SHOW TRIGGERS\G
接下来通过具体案例演示查看触发器。
例10-3 使用SHOW TRIGGERS语句查看所有触发器,SQL语句如下所示。
mysql> SHOW TRIGGERS\G
*************************** 1. row ***************************
Trigger: t_afterinsert_on_test1
Event: INSERT
Table: test1
Statement: BEGIN
INSERT INTO test2(id,name) values(new.id,new.name);
END
Timing: AFTER
Created: NULL
sql_mode:
Definer: root@localhost
character_set_client: gbk
collation_connection: gbk_chinese_ci
Database Collation: utf8_general_ci
*************************** 2. row ***************************
Trigger: t_afterdelete_on_test1
Event: DELETE
Table: test1
Statement: BEGIN
DELETE FROM test2 WHERE id=OLD.id;
END
Timing: AFTER
Created: NULL
sql_mode:
Definer: root@localhost
character_set_client: gbk
collation_connection: gbk_chinese_ci
Database Collation: utf8_general_ci
2 rows in set (0.03 sec)
以上执行结果可看到数据库中有两个触发器,通过SHOW TRIGGERS语句可以查看到两个触发器的Event、Table、Statement等。
2.从information_schema.triggers表中查看触发器
在MySQL中,触发器的信息存储在information_schema库下的triggers表中,可以通过查询该表的数据来查询触发器的信息,并且可以查询指定触发器的指定信息,操作更方便,接下来通过具体案例演示查看triggers表中触发器的信息,首先查看triggers的表结构,SQL语句如下所示。
mysql> DESC information_schema.triggers;
+--------------------------+---------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------------------------+---------------+------+-----+---------+-------+
| TRIGGER_CATALOG | varchar(512) | NO | | | |
| TRIGGER_SCHEMA | varchar(64) | NO | | | |
| TRIGGER_NAME | varchar(64) | NO | | | |
| EVENT_MANIPULATION | varchar(6) | NO | | | |
| EVENT_OBJECT_CATALOG | varchar(512) | NO | | | |
| EVENT_OBJECT_SCHEMA | varchar(64) | NO | | | |
| EVENT_OBJECT_TABLE | varchar(64) | NO | | | |
| ACTION_ORDER | bigint(4) | NO | | 0 | |
| ACTION_CONDITION | longtext | YES | | NULL | |
| ACTION_STATEMENT | longtext | NO | | NULL | |
| ACTION_ORIENTATION | varchar(9) | NO | | | |
| ACTION_TIMING | varchar(6) | NO | | | |
| ACTION_REFERENCE_OLD_TABLE | varchar(64) | YES | | NULL | |
| ACTION_REFERENCE_NEW_TABLE | varchar(64) | YES | | NULL | |
| ACTION_REFERENCE_OLD_ROW | varchar(3) | NO | | | |
| ACTION_REFERENCE_NEW_ROW | varchar(3) | NO | | | |
| CREATED | datetime | YES | | NULL | |
| SQL_MODE | varchar(8192) | NO | | | |
| DEFINER | varchar(77) | NO | | | |
| CHARACTER_SET_CLIENT | varchar(32) | NO | | | |
| COLLATION_CONNECTION | varchar(32) | NO | | | |
| DATABASE_COLLATION | varchar(32) | NO | | | |
+--------------------------+---------------+------+-----+---------+-------+
22 rows in set (0.01 sec)
以上执行结果可以看到triggers的表结构,包含Field、Type等信息,接下来通过具体案例演示通过information_schema.triggers表查看触发器的信息。
例10-4 通过information_schema.triggers表查看触发器t_afterdelete_on_test1的信息,SQL语句如下所示。
mysql> SELECT * FROM information_schema.triggers
-> WHERE trigger_name='t_afterdelete_on_test1'\G
*************************** 1. row ***************************
TRIGGER_CATALOG: def
TRIGGER_SCHEMA: qianfeng6
TRIGGER_NAME: t_afterdelete_on_test1
EVENT_MANIPULATION: DELETE
EVENT_OBJECT_CATALOG: def
EVENT_OBJECT_SCHEMA: qianfeng6
EVENT_OBJECT_TABLE: test1
ACTION_ORDER: 0
ACTION_CONDITION: NULL
ACTION_STATEMENT: BEGIN
DELETE FROM test2 WHERE id=OLD.id;
END
ACTION_ORIENTATION: ROW
ACTION_TIMING: AFTER
ACTION_REFERENCE_OLD_TABLE: NULL
ACTION_REFERENCE_NEW_TABLE: NULL
ACTION_REFERENCE_OLD_ROW: OLD
ACTION_REFERENCE_NEW_ROW: NEW
CREATED: NULL
SQL_MODE:
DEFINER: root@localhost
CHARACTER_SET_CLIENT: gbk
COLLATION_CONNECTION: gbk_chinese_ci
DATABASE_COLLATION: utf8_general_ci
1 row in set (0.06 sec)
从以上执行结果可看出,通过information_schema.triggers表查看到了触发器t_afterdelete_on_test1的详细信息,包括TRIGGER_SCHEMA、TRIGGER_NAME和EVENT_MANIPULATION等。
10.2.4 触发器使用的注意事项
触发器执行的语句有两个限制,具体如下所示。
触发器程序不能调用将数据返回客户端的存储程序,也不能使用采用CALL语句的动态SQL语句,但是允许存储过程通过参数将数据返回触发程序。也就是存储过程通过OUT或INOUT类型的参数可以将数据返回触发器,但不能调用直接返回数据的过程。
不能在触发器中使用以显式或隐式方式开始或结束事务的语句,如START TRANS-ACTION、COMMIT或ROLLBACK。
MySQL的触发器是按照BEFORE触发器、行操作、AFTER触发器的顺序执行的,其中任何一步发生错误都不会继续执行剩下的操作。如果对事务表进行的操作,如果出现错误,那么将会被回滚,如果是对非事务表进行操作,那么就无法回滚了,数据可能会出错。
10.2.5 删除触发器
在删除触发器时,当前用户必须具有删除触发器的权限。此时登录的是root用户,具体删除触发器的权限,这里就不再验证,删除触发器的语法格式如下所示。
DROP TRIGGER [IF EXISTS] [schema_name.]trigger_name
以上语法格式中,trigger_name是要删除的触发器名称,IF EXISTS是可选的,表示如果触发器不存在,不发生错误,而是产生一个警告,接下来通过具体案例演示删除触发器。
例10-5 将触发器t_afterdelete_on_test1删除,SQL语句如下所示。
mysql> DROP TRIGGER qianfeng6.t_afterdelete_on_test1;
Query OK, 0 rows affected (0.14 sec)
以上执行结果证明存储过程删除成功,可以使用SHOW TRIGGERS语句查看数据库中所有的触发器,SQL语句如下所示。
mysql> SHOW TRIGGERS\G
*************************** 1. row ***************************
Trigger: t_afterinsert_on_test1
Event: INSERT
Table: test1
Statement: BEGIN
INSERT INTO test2(id,name) values(new.id,new.name);
END
Timing: AFTER
Created: NULL
sql_mode:
Definer: root@localhost
character_set_client: gbk
collation_connection: gbk_chinese_ci
Database Collation: utf8_general_ci
1 row in set (0.01 sec)
从以上执行结果可看出,数据库中此时只有一个触发器t_afterinsert_on_test1,通过DROP TRIGGER语句,成功删除了触发器t_afterdelete_on_test1。
10.3 小案例
前面小节详细讲解了MySQL中触发器的使用,接下来通过一个小案例,演示使用触发器,更方便地实现数据完整性约束。
案例中有两张表,它们通过外键关联,当删除主表中的记录时,从表中对应的记录就没有意义了,利用触发器,自动将没有意义的数据删除,实现数据的完整性约束。
做案例之前,需要创建两张关联表,学生表student和交换生表bor_student,两张表之间通过外键stu_id关联,首先了解学生表student的表结构,如表10.3所示。
表10.3 student表
字段 |
字段类型 |
说明 |
stu_id |
int |
学生编号 |
stu_name |
varchar(30) |
学生姓名 |
stu_sex |
enum(‘m’,’f’) |
学生性别 |
表10.3中列出了student表的字段、字段类型和说明,接着创建student表,SQL语句如下所示。
mysql> CREATE TABLE student(
-> stu_id INT NOT NULL PRIMARY KEY,
-> stu_name VARCHAR(30) NOT NULL,
-> stu_sex enum('m','f') DEFAULT 'm'
-> );
Query OK, 0 rows affected (0.16 sec)
创建完成student表后,向表中插入数据,SQL语句如下所示。
mysql> INSERT INTO student
-> VALUES
-> (1,'zs','m'),
-> (2,'ls','f'),
-> (3,'ww','m');
Query OK, 3 rows affected (0.07 sec)
Records: 3 Duplicates: 0 Warnings: 0
以上执行结果证明数据插入完成,可以查看表中数据,SQL语句如下所示。
mysql> SELECT * FROM student;
+--------+----------+---------+
| stu_id | stu_name | stu_sex |
+--------+----------+---------+
| 1 | zs | m |
| 2 | ls | f |
| 3 | ww | m |
+--------+----------+---------+
3 rows in set (0.00 sec)
接着创建交换生表bor_student,表结构如表10.4所示。
表10.4 bor_student表
字段 |
字段类型 |
说明 |
bor_id |
int |
交换编号 |
stu_id |
int |
学生编号 |
bor_date |
date |
交换日期 |
ret_date |
date |
返回日期 |
表10.4中列出了bor_student表的字段、字段类型和说明,接着创建bor_student表,SQL语句如下所示。
mysql> CREATE TABLE bor_student(
-> bor_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
-> stu_id INT NOT NULL,
-> bor_date DATE,
-> ret_date DATE,
-> FOREIGN KEY(stu_id) REFERENCES student(stu_id)
-> );
Query OK, 0 rows affected (0.08 sec)
创建完成bor_student表后,向表中插入数据,SQL语句如下所示。
mysql> INSERT INTO bor_student
-> VALUES
-> (1001,1,'2017-01-01','2017-01-20'),
-> (1002,2,'2017-02-02','2017-03-01'),
-> (1003,3,'2017-08-11','2017-10-21');
Query OK, 3 rows affected (0.11 sec)
Records: 3 Duplicates: 0 Warnings: 0
以上执行结果证明数据插入完成,可以查看表中数据,SQL语句如下所示。
mysql> SELECT * FROM bor_student;
+--------+--------+------------+------------+
| bor_id | stu_id | bor_date | ret_date |
+--------+--------+------------+------------+
| 1001 | 1 | 2017-01-01 | 2017-01-20 |
| 1002 | 2 | 2017-02-02 | 2017-03-01 |
| 1003 | 3 | 2017-08-11 | 2017-10-21 |
+--------+--------+------------+------------+
3 rows in set (0.00 sec)
接下来编写触发器t_beforedelete,当student表中记录删除时,自动删除bor_student表中对应的数据,SQL语句如下所示。
mysql> delimiter //
mysql> CREATE TRIGGER t_beforedelete
-> BEFORE DELETE ON student FOR EACH ROW
-> BEGIN
-> DELETE FROM bor_student
-> WHERE bor_student.stu_id=OLD.stu_id;
-> END//
Query OK, 0 rows affected (0.08 sec)
此时删除学生表中stu_id为3的记录,SQL语句如下所示。
mysql> DELETE FROM student
-> WHERE stu_id=3;
Query OK, 1 row affected (0.04 sec)
以上执行结果证明表student中stu_id为3的记录成功删除,可以查看表中数据进行验证,SQL语句如下所示。
mysql> SELECT * FROM bor_student;
+--------+--------+------------+------------+
| bor_id | stu_id | bor_date | ret_date |
+--------+--------+------------+------------+
| 1001 | 1 | 2017-01-01 | 2017-01-20 |
| 1002 | 2 | 2017-02-02 | 2017-03-01 |
+--------+--------+------------+------------+
2 rows in set (0.00 sec)
表student中的记录删除后,查看表bor_student中对应的记录是否存在,SQL语句如下所示。
mysql> SELECT * FROM bor_student;
+--------+--------+------------+------------+
| bor_id | stu_id | bor_date | ret_date |
+--------+--------+------------+------------+
| 1001 | 1 | 2017-01-01 | 2017-01-20 |
| 1002 | 2 | 2017-02-02 | 2017-03-01 |
+--------+--------+------------+------------+
2 rows in set (0.00 sec)
从以上执行结果可看出,表bor_student中stu_id为3的记录被自动删除,这就是触发器在自动维护数据完整性。
10.4 本章小结
本章介绍了触发器的相关内容,首先讲解了触发器的概念,接着讲解了触发器的相关操作,包括触发器的创建、查看、使用和删除等,需要大家通过实践去熟练掌握。
10.5 习题
1.思考题
(1) 请简述什么是触发器。
(2) 请简述触发器的优点。
(3) 请简述触发器的作用。
(4) 请简述创建触发器的语法格式。
(5) 请简述删除触发器的语法格式。
- 点赞
- 收藏
- 关注作者
评论(0)