要创建触发器或删除触发器,请使用 CREATE TRIGGER
或 DROP TRIGGER
语句,描述在 第 15.1.22 节,“CREATE TRIGGER 语句” 和 第 15.1.34 节,“DROP TRIGGER 语句”。
以下是一个简单的示例,关联触发器与表,以激活 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
表。它还包括指定触发器激活时间、触发事件和要执行的操作的子句:
要使用触发器,设置累加器变量为零,执行 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 |
+-----------------------+
在这种情况下,@sum
的值在 INSERT
语句执行后是 14.98 + 1937.50 - 100
,或 1852.48
。
要销毁触发器,使用 DROP TRIGGER
语句。你必须指定模式名,如果触发器不在默认模式中:
mysql> DROP TRIGGER test.ins_sum;
如果你删除表,表的所有触发器也将被删除。
触发器名称存在于模式命名空间中,这意味着所有触发器在一个模式中必须具有唯一的名称。不同模式中的触发器可以具有相同的名称。
可以定义多个触发器,以便在给定表上具有相同的触发事件和激活时间。例如,你可以有两个 BEFORE UPDATE
触发器 для表。默认情况下,具有相同触发事件和激活时间的触发器将按照创建顺序激活。要影响触发器顺序,指定 FOR EACH ROW
之后的子句,指示 FOLLOWS
或 PRECEDES
和现有触发器的名称。使用 FOLLOWS
,新触发器将在现有触发器之后激活。使用 PRECEDES
,新触发器将在现有触发器之前激活。
例如,以下触发器定义定义了另一个 BEFORE INSERT
触发器 для account
表:
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
之后创建的。
在触发器体中,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
权限。这意味着您可以使用触发器来修改要插入新行或更新行的值。(在 AFTER
触发器中,SET
语句没有效果,因为行更改已经发生。)
在 BEFORE
触发器中,NEW
的值对于 AUTO_INCREMENT
列是 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
语句来调用它可能更容易。这也是从多个触发器中执行相同代码的优势。
触发器执行语句时存在限制:
-
触发器不能使用
CALL
语句来调用返回数据给客户端或使用动态 SQL 的存储过程。(存储过程可以通过OUT
或INOUT
参数将数据返回给触发器。) -
触发器不能使用明确或隐式开始或结束事务的语句,例如
START TRANSACTION
、COMMIT
或ROLLBACK
。(ROLLBACK to SAVEPOINT
是允许的,因为它不结束事务。)
MySQL 在触发器执行期间处理错误如下:
-
如果
BEFORE
触发器失败,对应行的操作将不执行。 -
BEFORE
触发器由尝试插入或修改行的操作激活,不管尝试是否最终成功。 -
一个
AFTER
触发器仅在任何BEFORE
触发器和行操作执行成功后执行。 -
在
BEFORE
或AFTER
触发器期间发生错误将导致引发触发器的整个语句失败。 -
对于事务表,语句的失败应该导致语句所做的所有更改回滚。触发器的失败将导致语句失败,因此触发器的失败也将导致回滚。对于非事务表,无法进行回滚,因此尽管语句失败,但在错误点之前所做的更改仍然生效。
触发器可以包含对表的直接引用,例如以下示例中命名为 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
(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)