触发器概念
什么是触发器
触发器是一种与表操作有关的数据库对象,当触发器所在表上发生指定事件时,将调用该对象,即表的操作事件触发表上的触发器的执行。
可以这样来理解:当某个表上的数据发生变化的时候,就会执行一个函数,这个函数可能会去执行一些对其他表的操作。这个函数就是触发器,它就相当于编程里的监听器一样,一旦监听到这个表发生了某些变化,就会执行已经写好的一套逻辑。按照面向对象的思想,这个触发器是该表的一个属性。
优缺点
参考:MySQL触发器的利弊
- 优点(常见应用场景)
- 基于行数据变更的日志记录。如:在用户订单系统中,我们可以基于用户订单数据状态的改变,使用触发器构建用户订单日志表数据。
- 基于行数据变更的关系数据的更新。如:用户订单改变至付款或相关状态时,我们可以基于用户订单数据状态的改变,使用触发器改变用户会付款或相应状态信息。
- 基于行数据变更的数据汇总。如:用户订单成交或失败,我们可以基于用户订单数据状态的改变,使用触发器构建用户总成交量或失败量汇总数据。
- 缺点
- MySQL触发器能基于行触发,MySQL触发器始终时基于表中的一条记录触发,而不是一组SQL语句。因此,如果需要变动整个数据集而数据集数据量又较大时,触发器效果会非常低。
- 一个MySQL触发器可能会关联到另外一张表或几张表的操作。因此,会导致数据库服务器负荷也会相应的增加一倍或几倍,如果出现因为触发器问题导致的性能问题,会很难定位问题位置和原因。
- 在基于锁的操作中,触发器可能会导致锁等待或死锁。触发器执行失败,原来执行的SQL语名也会执行失败。而因为触发器导致的失败结果和失败原因,往往很难排查。
创建触发器
该部分参考CREATE TRIGGER Syntax和 Trigger Syntax and Examples
一个定义在表
tbl_name
上的触发器会按照trigger_order
的顺序在该表记录发生了trigger_event
的trigger_time
时候,执行一套逻辑:trigger_body
。
CREATE
[DEFINER = { user | CURRENT_USER }]
TRIGGER trigger_name
trigger_time trigger_event
ON tbl_name FOR EACH ROW
[trigger_order]
trigger_body
trigger_time: { BEFORE | AFTER }
trigger_event: { INSERT | UPDATE | DELETE }
trigger_order: { FOLLOWS | PRECEDES } other_trigger_name
对以上创建语句:
-
tbl_name
触发器依附于表tbl_name
。这里的tbl_name
必须是持久化的表,而不能是临时表或者视图。 -
trigger_time
是指触发器何时执行,有两个取值: BEFORE 或者 AFTER 。 -
trigger_event
是指何种事件会激活该触发器,有三个取值: INSERT、 UPDATE 、DELETE。 -
INSERT
该表有新的记录插入的时候激活该触发器。比如insert
、load data
、replace
语句。 -
UPDATE
该表有记录被更新的时候激活该触发器。比如update
语句。 -
DELETE
该表有记录被删除的时候激活该触发器。比如delete
、replace
语句。但是,
drop table
、truncate table
语句不会激活该触发器,因为它们并没有使用delete
。 -
trigger_order
在5.7.2版本之前,对表tbl_name
,不能定义两个或以上触发事件和激活时间相同的触发器。但是,在5.7.2版本上,则可以定义多个触发事件和激活时间相同的触发器。(网上大多触发器的讲解都是说:不可以再同一个表创建两个触发事件和激活时间相同的触发器。一定要注意。)如果没有指定trigger_order
,则多个触发器会按照创建的先后而执行。如果指定了trigger_order
,则按照trigger_order
指定的顺序来执行。 -
trigger_body
这里是触发器要执行的逻辑。对多条语句,使用BEGIN...END。类似于自定义函数或存储过程中的写法。
在这里,可以使用两个关键字OLD
、NEW
(大小写不敏感),它们分别指代触发器影响前后的记录。 -
在INSERT事件类型的触发器中,只能使用
NEW.col_name
。因为没有旧记录。 -
在DELETE 事件类型的触发器中,只能使用
OLD.col_name
。因为没有新记录。 -
在UPDATE事件类型的触发器中,你可以使用
OLD.col_name
来引用更新前的记录的属性,使用NEW.col_name
来引用更新后的记录的属性。 -
使用
OLD.col_name
这种方式来引用的列是只读的,你不能修改它。在BEFOR类型的触发器中,你可以通过SET NEW.col_name = value
来改变使用NEW.col_name
来引用的列。这意味着,你可以使用触发器来修改一些将要被插入的数据。这并不会影响AFTER 类型的触发器,因为数据已经改变了。(意思是说,加入你想在插入数据的时候,对数据做一些处理,可以使用BEFOR类型的触发器。假如该表也同时定义了AFTER 类型的触发器,则AFTER 类型的触发器的trigger_body
的NEW.col_name
的值是BEFOR类型的触发器处理过的。两者并没有什么关系。) -
还要注意的是,在BEFOR类型的触发器中,假如某列是自增的(AUTO_INCREMENT),那么使用
NEW.col_name
这样的方式,将会返回0,而不是MySQL生成的正常的自增值。 -
与定义存储过程或者自定义函数一样,如果在BEGIN...END结构中,有多条语句,要使用delimiter 来定义语义分隔符,如:
mysql> delimiter //
mysql> CREATE TRIGGER upd_check BEFORE UPDATE ON account
FOR EACH ROW
BEGIN
IF NEW.amount < 0 THEN
SET NEW.amount = 0;
ELSEIF NEW.amount > 100 THEN
SET NEW.amount = 100;
END IF;
END;//
mysql> delimiter ;
```
* 在触发器的执行语句里,还有一些限制:
```
### 1
The trigger cannot use the CALL statement to invoke stored procedures
that return data to the client
or that use dynamic SQL.
(Stored procedures are permitted to return data to the trigger through OUT
or INOUT parameters.)
### 2
The trigger cannot use statements that explicitly or implicitly begin or end a transaction,
such as START TRANSACTION, COMMIT, or ROLLBACK.
(ROLLBACK to SAVEPOINT is permitted because it does not end a transaction.).
```
* 错误处理
* 如果BEFORE类型的触发器出错,激活该触发器的列的操作不会执行。
* BEFORE类型的触发器是由对列的插入或更新操作激活的,无论之后这些操作是否成功。
* 只有BEFORE触发器和随后的更新列操作成功后,AFTER 触发器才会被激活。
* 无论是BEFORE触发器还是AFTER 触发器出错,都会导致激活他们的那个操作失败。
* 对事务表,触发器会导致回滚。但是对于非事务表,不支持回滚,所以,如果语句执行失败,在失败之前的语句仍会有效。
---
### 删除触发器
```
DROP TRIGGER [IF EXISTS] [schema_name.]trigger_name
```
如果你执行`drop table`,那么该表的触发器也会同时被删除。
---
### 实例
例子1
```
# 准备工作
# 创建4张表,并在表test1上创建触发器testref
# 然后在表test3和表test4中初始化一些数据。
CREATE TABLE test1(a1 INT);
CREATE TABLE test2(a2 INT);
CREATE TABLE test3(a3 INT NOT NULL AUTO_INCREMENT PRIMARY KEY);
CREATE TABLE test4(
a4 INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
b4 INT DEFAULT 0
);
delimiter |
CREATE TRIGGER testref BEFORE INSERT ON test1
FOR EACH ROW
BEGIN
INSERT INTO test2 SET a2 = NEW.a1;
DELETE FROM test3 WHERE a3 = NEW.a1;
UPDATE test4 SET b4 = b4 + 1 WHERE a4 = NEW.a1;
END;
|
delimiter ;
INSERT INTO test3 (a3) VALUES
(NULL), (NULL), (NULL), (NULL), (NULL),
(NULL), (NULL), (NULL), (NULL), (NULL);
INSERT INTO test4 (a4) VALUES
(0), (0), (0), (0), (0), (0), (0), (0), (0), (0);
# 在表test1插入数据,就会激活触发器testref
mysql> INSERT INTO test1 VALUES
(1), (3), (1), (7), (1), (8), (4), (4);
Query OK, 8 rows affected (0.01 sec)
Records: 8 Duplicates: 0 Warnings: 0
# 最终,4张表的数据如下所示:
mysql> SELECT * FROM test1;
+------+
| a1 |
+------+
| 1 |
| 3 |
| 1 |
| 7 |
| 1 |
| 8 |
| 4 |
| 4 |
+------+
8 rows in set (0.00 sec)
mysql> SELECT * FROM test2;
+------+
| a2 |
+------+
| 1 |
| 3 |
| 1 |
| 7 |
| 1 |
| 8 |
| 4 |
| 4 |
+------+
8 rows in set (0.00 sec)
mysql> SELECT * FROM test3;
+----+
| a3 |
+----+
| 2 |
| 5 |
| 6 |
| 9 |
| 10 |
+----+
5 rows in set (0.00 sec)
mysql> SELECT * FROM test4;
+----+------+
| a4 | b4 |
+----+------+
| 1 | 3 |
| 2 | 0 |
| 3 | 1 |
| 4 | 2 |
| 5 | 0 |
| 6 | 0 |
| 7 | 1 |
| 8 | 1 |
| 9 | 0 |
| 10 | 0 |
+----+------+
10 rows in set (0.00 sec)
```
网友评论