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)
```
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,585评论 18 139
  • 1.1 基本结构 PL/SQL程序由三个块组成,即声明部分、执行部分、异常处理部分。 1.2 命名规则 1.3 记...
    慢清尘阅读 3,818评论 3 14
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,560评论 18 399
  • 触发器 MySQL包含对触发器的支持。触发器是一种与表操作有关的数据库对象,当触发器所在表上出现指定事件时,将调用...
    FTOLsXD阅读 916评论 0 1
  • 华灯初上的街 星空没有霓虹璀璨 寂静的窗台挡住了喧嚣的世界 思念的旋律组成了一首忧伤的歌 歌词里写不尽的愁绪 陪伴...
    惆怅君阅读 225评论 1 0