MySQL学习笔记(六):触发器


触发器概念

什么是触发器

触发器是一种与表操作有关的数据库对象,当触发器所在表上发生指定事件时,将调用该对象,即表的操作事件触发表上的触发器的执行。
可以这样来理解:当某个表上的数据发生变化的时候,就会执行一个函数,这个函数可能会去执行一些对其他表的操作。这个函数就是触发器,它就相当于编程里的监听器一样,一旦监听到这个表发生了某些变化,就会执行已经写好的一套逻辑。按照面向对象的思想,这个触发器是该表的一个属性。

优缺点

参考:MySQL触发器的利弊

  • 优点(常见应用场景)
  • 基于行数据变更的日志记录。如:在用户订单系统中,我们可以基于用户订单数据状态的改变,使用触发器构建用户订单日志表数据。
  • 基于行数据变更的关系数据的更新。如:用户订单改变至付款或相关状态时,我们可以基于用户订单数据状态的改变,使用触发器改变用户会付款或相应状态信息。
  • 基于行数据变更的数据汇总。如:用户订单成交或失败,我们可以基于用户订单数据状态的改变,使用触发器构建用户总成交量或失败量汇总数据。
  • 缺点
  • MySQL触发器能基于行触发,MySQL触发器始终时基于表中的一条记录触发,而不是一组SQL语句。因此,如果需要变动整个数据集而数据集数据量又较大时,触发器效果会非常低。
  • 一个MySQL触发器可能会关联到另外一张表或几张表的操作。因此,会导致数据库服务器负荷也会相应的增加一倍或几倍,如果出现因为触发器问题导致的性能问题,会很难定位问题位置和原因。
  • 在基于锁的操作中,触发器可能会导致锁等待或死锁。触发器执行失败,原来执行的SQL语名也会执行失败。而因为触发器导致的失败结果和失败原因,往往很难排查。

创建触发器

该部分参考CREATE TRIGGER Syntax和 Trigger Syntax and Examples

一个定义在表tbl_name上的触发器会按照trigger_order的顺序在该表记录发生了trigger_eventtrigger_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
    该表有新的记录插入的时候激活该触发器。比如insertload datareplace语句。

  • UPDATE
    该表有记录被更新的时候激活该触发器。比如update语句。

  • DELETE
    该表有记录被删除的时候激活该触发器。比如deletereplace语句。但是,
    drop tabletruncate table语句不会激活该触发器,因为它们并没有使用delete

  • trigger_order
    在5.7.2版本之前,对表tbl_name,不能定义两个或以上触发事件和激活时间相同的触发器。但是,在5.7.2版本上,则可以定义多个触发事件和激活时间相同的触发器。(网上大多触发器的讲解都是说:不可以再同一个表创建两个触发事件和激活时间相同的触发器。一定要注意。)如果没有指定trigger_order,则多个触发器会按照创建的先后而执行。如果指定了trigger_order,则按照trigger_order指定的顺序来执行。

  • trigger_body
    这里是触发器要执行的逻辑。对多条语句,使用BEGIN...END。类似于自定义函数或存储过程中的写法。
    在这里,可以使用两个关键字OLDNEW(大小写不敏感),它们分别指代触发器影响前后的记录。

  • 在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_bodyNEW.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)
```

你可能感兴趣的:(MySQL学习笔记(六):触发器)