使用触发器
触发器是一个与表相关联的命名数据库对象,在表发生特定事件时激活。触发器的一些用途是检查将要插入到表中的值,或者对更新中涉及的值 进行计算。
定义了一个触发器,当一条语句插入、更新或删除关联表中的行时激活。这些行操作是触发事件。例如,可以通过INSERT或LOAD DATA语句插入 行,并且为每个插入行激活一个INSERT触发器。触发器可以设置为在触发事件之前或之后激活。例如,可以在插入到表中的每一行之前或更新每 一行之后激活触发器。
MySQL触发器仅在SQL语句对表进行更改时激活。这包括对基于可更新视图的基表的更改。如果api没有将SQL语句传输到MySQL服务器,则触发器 不会激活对表的更改。这意味着使用NDB API进行的更新不会激活触发器。
在INFORMATION_SCHEMA或performance_schema表中更改不会激活触发器。那些表实际上是视图,视图上不允许有触发器。
下面几节描述创建和删除触发器的语法,展示如何使用它们的一些示例,并指出如何获取触发器元数据。
额外资源
.在使用触发器时,您可以找到使用触发器的触发器用户论坛。
.有关MySQL中触发器的常见问题的答案
.对触发器的使用有一些限制
.触发器的二进制日志记录按所述进行
触发器语法和示例
要创建触发器或删除触发器,请使用create trigger或drop trigger语句
下面是一个简单的示例,它将触发器与表关联起来,以激活INSERT操作。触发器充当累加器,将插入到表的某一列中的值相加。
mysql> CREATE TABLE account (acct_num INT, amount DECIMAL(10,2)); Query OK, 0 rows affected (0.01 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表相关联。它还包括指定触发器动作时间、触发事件以及触发器激活时 要做什么的子句:
.关键字BEFORE表示触发动作时间。在本例中,触发器在将每一行插入表之前激活。这里允许的另一个关键字是AFTER。
.关键字INSERT表示触发事件;也就是说,激活触发器的操作类型。在本例中,INSERT操作导致触发器激活。您还可以为DELETE和UPDATE操作创 建触发器。
.FOR EACH ROW后面的语句定义了触发器体;也就是说,每次触发器激活时执行的语句,对于受触发事件影响的每一行执行一次。在本例中,触 发器主体是一个简单的SET,它将插入到amount列中的值累积到一个用户变量中。语句将列引用为NEW。Amount,表示“要插入到新行中的金额列 的值”。
要使用触发器,将累加器变量设置为零,执行INSERT语句,然后查看变量之后的值:
mysql> SET @sum = 0; Query OK, 0 rows affected (0.00 sec) mysql> INSERT INTO account VALUES(137,14.98),(141,1937.50),(97,-100.00); Query OK, 3 rows affected (0.00 sec) Records: 3 Duplicates: 0 Warnings: 0 mysql> SELECT @sum AS 'Total amount inserted'; +-----------------------+ | Total amount inserted | +-----------------------+ | 1852.48 | +-----------------------+ 1 row in set (0.00 sec)
在本例中,执行INSERT语句后@sum的值为14.98 + 1937.50 – 100,即1852.48。
要销毁触发器,使用DROP trigger语句。如果触发器不在默认模式中,则必须指定模式名:
mysql> DROP TRIGGER test.ins_sum; Query OK, 0 rows affected (0.00 sec)
如果删除一个表,那么该表的任何触发器也会被删除。
触发器名称存在于模式名称空间中,这意味着在一个模式中所有触发器都必须具有唯一的名称。不同模式中的触发器可以具有相同的名称。
从 MySQL 5.7.2 版本开始,可以为给定的表定义多个具有相同触发事件和操作时间的触发器。例如,您可以为一个表定义两个 BEFORE UPDATE 触发器。默认情况下,具有相同触发事件和操作时间的触发器按照创建的顺序激活。要影响触发器的顺序,请在 FOR EACH ROW 之后指定一个子 句,该子句指示 FOLLOWS 或 PRECEDES 以及具有相同触发事件和操作时间的现有触发器的名称。使用 FOLLOWS 时,新触发器在现有触发器之后 激活。使用 PRECEDES 时,新触发器在现有触发器之前激活。
例如,下面的触发器定义为account表定义了另一个BEFORE INSERT触发器:
mysql> CREATE TRIGGER ins_sum BEFORE INSERT ON account FOR EACH ROW SET @sum = @sum + NEW.amount;
Query OK, 0 rows affected (0.00 sec)
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,但分别累积存款和取款。它有一个PRECEDES子句,使它在ins_sum之前激活;如果没有该子句,它 将在ins_sum之后激活,因为它是在ins_sum之后创建的。
在MySQL 5.7.2之前,对于一个给定的表,不能有多个触发器具有相同的触发事件和操作时间。例如,一个表不能有两个BEFORE UPDATE触发器。 为了解决这个问题,你可以定义一个执行多个语句的触发器,在FOR EACH FOW之后使用BEGIN…END复合语句构造。(本节后面会给出一个例子。 )
在触发器主体中,OLD和NEW关键字使您能够访问受触发器影响的行中的列。OLD和NEW是MySQL对触发器的扩展;它们不区分大小写。
在 INSERT 触发器中,只能使用 NEW.col_name;不存在旧行。在 DELETE 触发器中,只能使用 OLD.col_name;不存在新行。在 UPDATE 触发器 中,可以使用 OLD.col_name 来引用行更新前的列,使用 NEW.col_name 来引用行更新后的列。
以OLD命名的列是只读的。您可以引用它(如果您有SELECT权限),但不能修改它。如果您对以NEW命名的列具有SELECT权限,则可以引用该列。 在BEFORE触发器中,您还可以使用SET NEW.col_name = value更改其值,如果您对它有UPDATE权限。这意味着您可以使用触发器来修改要插入新 行或用于更新行的值。(这样的SET语句在AFTER触发器中没有作用,因为行更改已经发生了。)
在BEFORE触发器中,AUTO_INCREMENT列的NEW值是0,而不是实际插入新行时自动生成的序列号。
通过使用BEGIN … END构造时,你可以定义一个执行多个语句的触发器。在BEGIN语句块中,还可以使用存储例程允许的其他语法,如条件语句 和循环语句。然而,就像存储例程一样,如果你使用mysql程序定义了一个执行多条语句的触发器,那么就有必要重新定义mysql语句的定界符, 这样你就可以使用;触发器定义中的语句定界符。下面的例子说明了这些要点。它定义了一个更新触发器,检查用于更新每一行的新值,并将该 值修改为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;//
Query OK, 0 rows affected (0.00 sec)
mysql> delimiter ;
单独定义存储过程,然后使用简单的CALL语句从触发器调用它,这样更容易。如果你想在多个触发器中执行相同的代码,这也是有利的。
当触发器被激活时,可以在语句中显示的内容是有限制的
.触发器不能使用CALL语句调用向客户机返回数据或使用动态SQL的存储过程。(允许存储过程通过OUT或INOUT参数)。
.触发器不能使用显式或隐式开始或结束事务的语句,例如START TRANSACTION, COMMIT, ROLLBACK。(回滚到SAVEPOINT是允许的,因为它不 结束事务。)
MySQL在触发器执行过程中处理错误如下:
.如果BEFORE触发失败,则不执行对应行的操作。
.当尝试插入或修改该行时,无论随后的尝试是否成功,都会激活BEFORE触发器。
.只有当任何BEFORE触发器和行操作成功执行时,才会执行AFTER触发器。
.BEFORE或AFTER触发器期间的错误将导致触发触发器调用的整个语句失败。
.对于事务性表,语句失败应导致回滚该语句执行的所有更改。触发器失败会导致语句失败,因此触发器失败也会导致回滚。对于非事务性表, 不能进行这样的回滚,因此尽管语句失败,但在错误点之前执行的任何更改仍然有效。
触发器可以通过名称直接引用表,例如下面这个例子中的testref触发器:
mysql> CREATE TABLE test1(a1 INT);
Query OK, 0 rows affected (0.01 sec)
mysql> CREATE TABLE test2(a2 INT);
Query OK, 0 rows affected (0.01 sec)
mysql> CREATE TABLE test3(a3 INT NOT NULL AUTO_INCREMENT PRIMARY KEY);
Query OK, 0 rows affected (0.01 sec)
mysql> CREATE TABLE test4(
-> a4 INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
-> b4 INT DEFAULT 0
-> );
Query OK, 0 rows affected (0.01 sec)
mysql> delimiter |
mysql> 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;
-> |
Query OK, 0 rows affected (0.00 sec)
mysql> delimiter ;
mysql> INSERT INTO test3 (a3) VALUES
-> (NULL), (NULL), (NULL), (NULL), (NULL),
-> (NULL), (NULL), (NULL), (NULL), (NULL);
Query OK, 10 rows affected (0.00 sec)
Records: 10 Duplicates: 0 Warnings: 0
mysql> INSERT INTO test4 (a4) VALUES
-> (0), (0), (0), (0), (0), (0), (0), (0), (0), (0);
Query OK, 10 rows affected (0.00 sec)
Records: 10 Duplicates: 0 Warnings: 0
假设在表test1中插入以下值,如下所示:
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
因此,这四个表包含以下数据:
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)
触发元数据
可以通过以下方式获取触发器的元数据:
.查询INFORMATION_SCHEMA数据库中的TRIGGERS表
.使用SHOW CREATE TRIGGER语句。
.使用SHOW TRIGGERS语句