要创建触发器或删除触发器,请使用 CREATE TRIGGER
或 DROP TRIGGER
语句,如 第 15.1.22 节,“CREATE TRIGGER 语句” 和 第 15.1.34 节,“DROP TRIGGER 语句” 中所述。
以下是一个简单的示例,它将触发器与表关联,以激活 INSERT
操作。触发器充当累加器,对插入到表其中一列的值求和。
Press CTRL+C to copymysql> 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
语句,然后查看该变量之后的值。
Press CTRL+C to copymysql> 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
语句。如果触发器不在默认模式中,则必须指定模式名。
Press CTRL+C to copymysql> DROP TRIGGER test.ins_sum;
如果删除表,则也会删除该表的任何触发器。
触发器名称存在于模式命名空间中,这意味着所有触发器在一个模式内必须具有唯一的名称。不同模式中的触发器可以具有相同的名称。
对于给定的表,可以定义多个具有相同触发事件和动作时间的触发器。例如,您可以为一个表创建两个 BEFORE UPDATE
触发器。默认情况下,具有相同触发事件和动作时间的触发器按创建顺序激活。要影响触发器顺序,请在 FOR EACH ROW
之后指定一个子句,该子句指示 FOLLOWS
或 PRECEDES
以及具有相同触发事件和动作时间的现有触发器的名称。使用 FOLLOWS
,新触发器将在现有触发器之后激活。使用 PRECEDES
,新触发器将在现有触发器之前激活。
例如,以下触发器定义为 account
表定义了另一个 BEFORE INSERT
触发器。
Press CTRL+C to copymysql> 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
触发器中,如果您拥有该列的 UPDATE
权限,您还可以使用 SET NEW.
更改其值。这意味着您可以使用触发器修改要插入新行或用于更新行的值。(此类 col_name
= value
SET
语句在 AFTER
触发器中没有作用,因为行更改已经发生。)
在 BEFORE
触发器中,AUTO_INCREMENT
列的 NEW
值为 0,而不是在实际插入新行时自动生成的序列号。
通过使用 BEGIN ... END
结构,您可以定义一个执行多条语句的触发器。在 BEGIN
块中,您还可以使用存储例程中允许的其他语法,例如条件语句和循环。但是,就像存储例程一样,如果您使用 mysql 程序定义执行多条语句的触发器,则需要重新定义 mysql 语句分隔符,以便您可以在触发器定义中使用 ;
语句分隔符。以下示例说明了这些要点。它定义了一个 UPDATE
触发器,该触发器检查要用于更新每一行的新的值,并将该值修改为在 0 到 100 的范围内。这必须是一个 BEFORE
触发器,因为必须在使用该值更新行之前检查该值。
Press CTRL+C to copymysql> 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.8 节,“存储程序的限制”。
MySQL 处理触发器执行期间的错误如下
如果
BEFORE
触发器失败,则不会对相应行执行操作。BEFORE
触发器由插入或修改行的尝试激活,无论该尝试是否随后成功。只有当所有
BEFORE
触发器和行操作成功执行时,才会执行AFTER
触发器。在
BEFORE
或AFTER
触发器期间发生的错误会导致导致触发器调用的整个语句失败。对于事务表,语句失败会导致语句执行的所有更改回滚。触发器失败会导致语句失败,因此触发器失败也会导致回滚。对于非事务表,无法执行此类回滚,因此尽管语句失败,但错误发生之前执行的所有更改仍然有效。
触发器可以包含对表的直接引用,例如本示例中名为 testref
的触发器
Press CTRL+C to copyCREATE 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
,如这里所示
Press CTRL+C to copymysql> 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
因此,四个表包含以下数据
Press CTRL+C to copymysql> 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)