文档首页
MySQL 9.0 参考手册
相关文档 下载此手册
PDF (US Ltr) - 40.0Mb
PDF (A4) - 40.1Mb
手册页 (TGZ) - 258.2Kb
手册页 (Zip) - 365.3Kb
Info (Gzip) - 4.0Mb
Info (Zip) - 4.0Mb


MySQL 9.0 参考手册  /  ...  /  触发器语法和示例

27.4.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 |
+-----------------------+

在本例中,INSERT 语句执行后,@sum 的值为 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_transactionins_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 权限),但不能修改它。如果您具有 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 的存储过程。(存储过程可以通过OUTINOUT参数将数据返回给触发器。)

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

另请参见第 27.9 节,“存储程序限制”

MySQL 处理触发器执行期间发生的错误的方式如下

  • 如果BEFORE触发器失败,则不会对相应行执行操作。

  • BEFORE触发器由尝试插入或修改行激活,无论该尝试随后是否成功。

  • 只有在任何BEFORE触发器和行操作成功执行的情况下,才会执行AFTER触发器。

  • 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)