MySQL学习——触发器的语法和示例

触发器(trigger)是数据库中的一个重要概念。触发器是与表相关联的命名数据库对象,当表上发生特定事件时,触发器会被激活。

触发器定义为在语句插入、更新或删除关联表中的行时激活。这些行操作是触发事件。例如,可以通过INSERT或LOAD DATA语句插入行,并为每个插入的行激活插入触发器。触发器可以设置为在触发事件之前或之后激活。例如,可以在表中插入的每一行之前或更新的每一行都激活触发器。

MySQL触发器仅针对SQL语句对表所做的更改而激活。这包括对作为可更新视图基础的基表的更改。对于不将SQL语句传输到MySQL Server的API对表所做的更改,触发器不会激活。这意味着触发器不会被使用NDB API进行的更新激活。

INFORMATION_SCHEMA或performance_SCHEMA表中的更改不会激活触发器。这些表实际上是视图,不允许在视图上使用触发器。

以下部分描述了创建和删除触发器的语法,显示了一些如何使用触发器的示例,并指示了如何获取触发器元数据。

这里有一个简单的例子,它将触发器与表相关联,以激活INSERT操作。触发器充当累加器,将插入表的某一列的值相加。

mysql> CREATE TABLE account (acct_num INT, amount DECIMAL(10,2));

Query OK, 0 rows affected (0.03 sec) 



mysql> CREATE TRIGGER ins_sum BEFORE INSERT ON account 

FOR EACH ROW SET @sum = @sum + NEW.amount; 

Query OK, 0 rows affected (0.01 sec)

CREATE TRIGGER语句创建一个名为ins_sum的触发器,该触发器与account表相关联。它还包括指定触发器动作时间、触发事件以及触发器激活时要做什么的条款:

  1. 关键字BEFORE表示触发器动作时间。在这种情况下,触发器会在每一行插入表之前激活。这里允许的另一个关键字是AFTER。
  2. 关键字INSERT表示触发事件;即激活触发器的操作类型。在该示例中,INSERT操作会导致触发器激活。您还可以为DELETE和UPDATE操作创建触发器。
  3. FOR EACH ROW后面的语句定义了触发器主体;也就是说,每次触发器激活时要执行的语句,对于受触发事件影响的每一行都会发生一次。在该示例中,触发器主体是一个简单的SET,它将插入金额列的值累积到用户变量中。该语句将该列称为NEW.amount,意思是"要插入新行的金额列的值"

要使用触发器,请将accumulator变量设置为零,执行INSERT语句,然后查看变量的值:

mysql> SET @sum = 0; 

mysql> INSERT INTO account VALUES(137,14.98),(141,1937.50),(97,-100.00); 

mysql> SELECT @sum AS 'Total amount inserted'; 

+-----------------------+ 

| Total amount inserted | 

+-----------------------+ 

|                1852.48 | 

+-----------------------+

在这种情况下,INSERT语句执行后@sum的值为14.98+1937.50-100,或1852.48。

要销毁触发器,请使用DROP trigger语句。如果触发器不在默认架构中,则必须指定架构名称:

mysql> DROP TRIGGER test.ins_sum;

如果您删除了一个表,那么该表的所有触发器也会被删除。

触发器名称存在于架构命名空间中,这意味着所有触发器在架构中都必须具有唯一的名称。不同架构中的触发器可以具有相同的名称。

从MySQL 5.7.2开始,可以为给定的表定义多个具有相同触发事件和操作时间的触发器。例如,一个表可以有两个BEFORE UPDATE触发器。默认情况下,具有相同触发事件和操作时间的触发器按创建顺序激活。若要影响触发器顺序,请在FOR EACH ROW后面指定一个子句,该子句指示FOLLOWS或PRECEDES以及具有相同触发器事件和操作时间的现有触发器的名称。使用FOLLOWS,新触发器在现有触发器之后激活。使用PRECEDES,新触发器会在现有触发器之前激活。

例如,以下触发器定义为帐户表定义了另一个BEFORE INSERT触发器:

mysql> CREATE TRIGGER ins_transaction BEFORE INSERT ON account 

FOR EACH ROW PRECEDES ins_sum

SET 

@deposits = @deposits + IF(NEW.amount>0,NEW.amount,0), 

@withdrawals = @withdrawals + IF(NEW.amount<0,-NEW.amount,0); 

Query OK, 0 rows affected (0.01 sec)

触发器ins_transaction类似于ins_sum,但分别累积存储和提取。它有一个PRECEDS子句,使它在ins_sum之前激活;如果没有该子句,它将在inssum之后激活,因为它是在inssums之后创建的。

在MySQL 5.7.2之前,给定表不能有多个触发器具有相同的触发事件和操作时间。例如,一个表不能有两个BEFORE UPDATE触发器。要解决此问题,可以使用BEGIN...END定义一个执行多个语句的触发器。。。在FOR EACH ROW之后结束复合语句构造。

在触发器主体中,OLD和NEW关键字使您能够访问受触发器影响的行中的列。OLD和NEW是触发器的MySQL扩展;它们不区分大小写。

在INSERT触发器中,只能使用NEW.col_name;没有旧行。在DELETE触发器中,只能使用OLD.col_name;没有新行。在UPDATE触发器中,可以使用OLD.col_name引用行更新前的列,使用NEW.col_name引用行更新后的列。

以OLD命名的列是只读的。您可以引用它(如果您拥有SELECT特权),但不能修改它。如果您拥有它的SELECT特权,您可以引用以NEW命名的列。在BEFORE触发器中,如果您拥有UPDATE特权,您也可以使用SET NEW.col_name=value更改它的值。这意味着您可以使用触发器修改要插入新行或用于更新行的值。(这样的SET语句在AFTER触发器中无效,因为行更改已经发生。在BEFORE触发器中,AUTO_INCREMENT列的NEW值为0,而不是实际插入新行时自动生成的序列号。

通过使用BEGIN。。。END构造,您可以定义一个执行多个语句的触发器。在BEGIN块中,您还可以使用存储例程中允许的其他语法,如条件语句和循环。然而,正如存储例程一样,如果您使用mysql程序定义一个执行多条语句的触发器,则有必要重新定义mysql语句分隔符,以便使用;触发器定义中的语句分隔符。以下示例说明了这些要点。它定义了一个UPDATE触发器,用于检查用于更新每一行的新值,并将该值修改为在0到100的范围内。这必须是BEFORE触发器,因为在使用该值更新行之前必须检查该值:

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 ;

可以更容易地单独定义存储过程,然后使用简单的CALL语句从触发器调用它。如果您想在多个触发器中执行相同的代码,这也是有利的。

触发器在激活时执行的语句中可能出现的内容存在限制:

  1. 触发器不能使用CALL语句来调用向客户端返回数据或使用动态SQL的存储过程。(允许存储过程通过OUT或INOUT参数将数据返回到触发器)
  2. 触发器不能使用显式或隐式开始或结束事务的语句,如START transaction、COMMIT或ROLLBACK。(允许ROLLBACK到SAVEPOINT,因为它不会结束事务)。

MySQL处理触发器执行过程中的错误如下:

  1. 如果BEFORE触发器失败,则不执行对相应行的操作。
  2. BEFORE触发器由插入或修改行的尝试激活,无论该尝试随后是否成功。
  3. 只有当任何BEFORE触发器和行操作成功执行时,才执行AFTER触发器。
  4. BEFORE或AFTER触发器期间的错误会导致导致触发器调用的整个语句失败。
  5. 对于事务表,语句的失败应该会导致该语句执行的所有更改的回滚。触发器失败会导致语句失败,因此触发器失败也会导致回滚。对于非事务表,不能进行这样的回滚,因此,尽管语句失败,但在错误点之前执行的任何更改仍然有效。

触发器可以包含按名称对表的直接引用,例如本例中显示的名为testref的触发器:

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中,如下所示:

mysql> INSERT INTO test1 VALUES 

, (3), (1), (7), (1), (8), (4), (4); 
Query OK, 8 rows affected (0.01 sec)

Records: 8 Duplicates: 0 Warnings: 0

因此,这四个表包含以下数据:

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)
相关推荐
奥顺互联V几秒前
一次性部署:使用Docker部署PHP应用
大数据·mysql·开源·php
向阳12183 分钟前
mybatis 动态 SQL
数据库·sql·mybatis
胡图蛋.5 分钟前
什么是事务
数据库
小黄人软件7 分钟前
20241220流水的日报 mysql的between可以用于字符串 sql 所有老日期的,保留最新日期
数据库·sql·mysql
张声录112 分钟前
【ETCD】【实操篇(三)】【ETCDCTL】如何向集群中写入数据
数据库·chrome·etcd
无为之士18 分钟前
Linux自动备份Mysql数据库
linux·数据库·mysql
小汤猿人类31 分钟前
open Feign 连接池(性能提升)
数据库
阳冬园1 小时前
mysql数据库 主从同步
数据库·主从同步
XiaoH2331 小时前
培训机构Day15
sql·mysql
biter00881 小时前
opencv(15) OpenCV背景减除器(Background Subtractors)学习
人工智能·opencv·学习