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)
相关推荐
mqiqe13 分钟前
Python MySQL通过Binlog 获取变更记录 恢复数据
开发语言·python·mysql
工业甲酰苯胺15 分钟前
MySQL 主从复制之多线程复制
android·mysql·adb
BestandW1shEs16 分钟前
谈谈Mysql的常见基础问题
数据库·mysql
重生之Java开发工程师18 分钟前
MySQL中的CAST类型转换函数
数据库·sql·mysql
教练、我想打篮球20 分钟前
66 mysql 的 表自增长锁
数据库·mysql
Ljw...22 分钟前
表的操作(MySQL)
数据库·mysql·表的操作
哥谭居民000122 分钟前
MySQL的权限管理机制--授权表
数据库
wqq_99225027732 分钟前
ssm旅游推荐系统的设计与开发
数据库·旅游
难以触及的高度1 小时前
mysql中between and怎么用
数据库·mysql