要创建触发器或删除触发器,请使用 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
表关联。它还包括指定触发器操作时间、触发事件以及触发器激活时要执行的操作的子句。
关键字
BEFORE
指示触发器操作时间。在本例中,触发器在向表中插入的每一行之前激活。这里允许的另一个关键字是AFTER
。关键字
INSERT
指示触发事件;即激活触发器的操作类型。在本例中,INSERT
操作会导致触发器激活。您还可以为DELETE
和UPDATE
操作创建触发器。紧随
FOR EACH ROW
的语句定义触发器主体;即每次触发器激活时要执行的语句,它会为触发事件影响的每一行执行一次。在本例中,触发器主体是一个简单的SET
,它将插入到amount
列中的值累加到用户变量中。该语句将列称为NEW.amount
,这意味着 “要插入到新行中的amount
列的值。”
要使用触发器,请将累加器变量设置为零,执行一个 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;
如果删除了表,则该表的任何触发器也会被删除。
触发器名称存在于架构命名空间中,这意味着所有触发器在架构内必须具有唯一的名称。不同架构中的触发器可以具有相同的名称。
可以为给定的表定义多个具有相同触发事件和操作时间的触发器。例如,您可以为一个表创建两个 BEFORE UPDATE
触发器。默认情况下,具有相同触发事件和操作时间的触发器将按照创建顺序激活。要影响触发器顺序,请在 FOR EACH ROW
后面指定一个子句,指示 FOLLOWS
或 PRECEDES
以及具有相同触发事件和操作时间的现有触发器的名称。使用 FOLLOWS
,新触发器将在现有触发器之后激活。使用 PRECEDES
,新触发器将在现有触发器之前激活。
例如,以下触发器定义为 account
表定义了另一个 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
类似,但分别累加存款和取款。它有一个 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
权限),但不能修改它。如果您具有 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
语句从触发器中调用它。如果您希望从多个触发器中执行相同的代码,这也很有利。
对触发器激活时执行的语句中可以出现的内容有限制。
触发器不能使用
CALL
语句调用将数据返回给客户端或使用动态 SQL 的存储过程。(存储过程可以通过OUT
或INOUT
参数将数据返回给触发器。)触发器不能使用显式或隐式开始或结束事务的语句,例如
START TRANSACTION
、COMMIT
或ROLLBACK
。(ROLLBACK to SAVEPOINT
是允许的,因为它不会结束事务。)
另请参见第 27.9 节,“存储程序限制”。
MySQL 处理触发器执行期间发生的错误的方式如下
如果
BEFORE
触发器失败,则不会对相应行执行操作。BEFORE
触发器由尝试插入或修改行激活,无论该尝试随后是否成功。只有在任何
BEFORE
触发器和行操作成功执行的情况下,才会执行AFTER
触发器。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)