MySQL 8.4 Reference Manual  /  ...  /  Trigger Syntax and Examples

27.3.1 触发器语法和示例

要创建触发器或删除触发器,请使用CREATE TRIGGERDROP 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操作激活触发器。您也可以创建触发器来处理DELETEUPDATE操作。

  • 语句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 |
+-----------------------+

在这个例子中,@sum变量的值在INSERT语句执行后为14.98 + 1937.50 - 100,或1852.48

要销毁触发器,请使用DROP TRIGGER语句。您必须指定架构名,如果触发器不在默认架构中:

mysql> DROP TRIGGER test.ins_sum;

如果您删除一个表,任何该表的触发器也将被删除。

触发器名称存在于架构命名空间中,这意味着所有触发器必须在架构中具有唯一的名称。来自不同架构的触发器可以具有相同的名称。

可以为给定的表定义多个触发器,这些触发器具有相同的触发事件和动作时间。例如,您可以为一个表定义两个BEFORE UPDATE触发器。默认情况下,具有相同触发事件和动作时间的触发器将按照它们的创建顺序激活。要影响触发器顺序,请在FOR EACH ROW后指定一个子句,该子句指示FOLLOWSPRECEDES,并指定一个具有相同触发事件和动作时间的现有触发器的名称。使用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之后的。

在触发器体中,OLDNEW关键字使您可以访问受触发器影响的行的列。OLDNEW是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的存储程序。 (存储程序允许通过OUTINOUT参数将数据返回到触发器。)

  • 触发器不能使用隐式或显式地开始或结束事务的语句,如START TRANSACTIONCOMMITROLLBACK。 (ROLLBACK to SAVEPOINT允许,因为它不结束事务。)

请参见第27.8节,“存储程序限制”.

MySQL 处理触发器执行错误如下:

  • 如果一个BEFORE触发器失败,操作对应行将不执行。

  • 一个BEFORE触发器在尝试插入或修改行时被激活,即使尝试最终失败。

  • 一个AFTER触发器只有在所有BEFORE触发器和行操作都成功执行时才被执行。

  • BEFOREAFTER触发器出现错误时,整个导致触发器调用的语句将失败。

  • 对于事务表,语句失败应该导致所有由语句执行的更改回滚。触发器失败也会导致语句失败,从而导致回滚。对于非事务表,回滚不能执行,因此尽管语句失败,但是在错误点之前执行的所有更改仍然生效。

触发器可以包含对表名的直接引用,例如在以下示例中所示的触发器名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)