跳到主要内容

DECLARE ... HANDLER

DECLARE ... HANDLER 语句用于指定处理一个或多个异常条件的处理程序。如果出现某一条件,就会执行指定的语句。

语法与参数说明

处理程序声明必须出现在变量或条件声明之后。DECLARE ... HANDLER 语句的语法如下:

DECLARE handler_action HANDLER
FOR condition_value [, condition_value] ...
statement

handler_action: {
CONTINUE
| EXIT
| UNDO
}

condition_value: {
mysql_error_code
| SQLSTATE [VALUE] sqlstate_value
| condition_name
| SQLWARNING
| NOT FOUND
| SQLEXCEPTION
}

statement 可以是简单的语句,例如 SET var_name = 1,也可以是使用 BEGIN ... END 块编写的复合语句。

handler_action 指示处理程序在执行处理程序语句后采取的操作,详情如下:

  • CONTINUE:继续执行当前程序。

  • EXIT:表示处理程序的 BEGIN ... END 块中的 Statement 执行完后,退出本次存储过程的执行。

  • UNDO:不支持。

condition_value 指示激活处理程序的特定条件或条件类别。它可以采取以下形式:

  • mysql_error_code:表示错误代码的整数字面量,例如 5217 表示"unknown column"。

  • SQLWARNING:以 '01' 开头的 SQLSTATE 值的简写。

  • condition_name:先前使用 DECLARE ... CONDITION 指定的条件名称。条件名称可以与错误代码或 SQLSTATE 值相关联。

  • NOT FOUND:以 '02' 开头的 SQLSTATE 值的简写。这与游标的上下文相关,用于控制游标到达数据集末尾时发生的情况。如果没有更多行可用,则会出现 SQLSTATE 值为"02000"的 No Data 条件。要检测这种情况,您可以为其设置处理程序或为 NOT FOUND 条件设置处理程序。

  • SQLEXCEPTION:不以"00"、"01"或"02"开头的 SQLSTATE 值的简写。

如果出现未声明处理程序的条件,则根据条件的类别采取如下操作:

  • 对于 SQLEXCEPTION 条件,存储程序在引发条件的语句处终止,类似于使用了一个 EXIT 处理程序。如果该程序被另一个存储程序调用,则调用程序会使用自己的选择规则来处理该条件。

  • 对于 SQLWARNING 条件,程序会继续执行,类似于使用了一个 CONTINUE 处理程序。

  • 对于 NOT FOUND 条件,如果条件被正常引发,则操作为 CONTINUE。如果它是由 SIGNALRESIGNAL 引发的,则动作为 EXIT

如下示例中,由于使用了 DECLARE ... HANDLER 语句声明了当出现重复键错误时执行 CONTINUE 操作,所以即使有 PRIMARY KEY 的约束,在第二次 INSERT 失败后并没有采取默认的 EXIT 操作,导致最后的执行结果为 3。

CREATE TABLE test.tbl1 (c1 INT, PRIMARY KEY (c1));
Query OK, 0 rows affected

delimiter //

CREATE PROCEDURE handler_demo()
BEGIN
DECLARE CONTINUE HANDLER FOR SQLSTATE '23000' SET @x = 1;
INSERT INTO test.tbl1 VALUES (101);
SET @x = 2;
INSERT INTO test.tbl1 VALUES (101);
SET @x = 3;
END;
//
Query OK, 0 rows affected

delimiter ;

CALL handler_demo();
Query OK, 0 rows affected

SELECT @x;
+------+
| @x |
+------+
| 3 |
+------+
1 row in set

如果需要忽略某个条件,可以为其声明一个 CONTINUE 处理程序并将其与一个空块相关联。示例如下:

DECLARE CONTINUE HANDLER FOR SQLWARNING BEGIN END;

块标签的使用限制

块标签的范围不包括块内声明的处理程序的代码。因此,与处理程序关联的语句不能使用 ITERATELEAVE 来引用包含处理程序声明的块标签。

如下示例中,REPEAT 块具有 retry_lable 标签,而 retry_lable 标签在块内的 IF 语句的范围内,但它不在 CONTINUE 处理程序的范围内,因此 REPEAT 块引用 retry_lable 标签无效并导致错误。

CREATE PROCEDURE proc1 ()
BEGIN
DECLARE n INT DEFAULT 10;
retry_lable:
REPEAT
BEGIN
DECLARE CONTINUE HANDLER FOR SQLWARNING
BEGIN
ITERATE retry_lable; # 非法标签
END;
IF n < 0 THEN
LEAVE retry_lable; # 合法标签
END IF;
SET n = n - 1;
END;
UNTIL FALSE END REPEAT;
END //

ERROR 1308 (42000): no matching label: retry_lable

使用以下策略可以避免在处理程序中引用外部标签:

  • 使用 EXIT 处理程序离开该块,并将清理语句放在处理程序主体中。如果不需要块清理,则 BEGIN ... END 所包含的处理程序主体可以为空。示例如下:

    DECLARE EXIT HANDLER FOR SQLWARNING
    BEGIN
    block cleanup statements # 块清理语句
    END;
  • 要继续执行,请在 CONTINUE 处理程序中设置一个状态变量,通过在封闭块中检查该状态变量以确定是否调用了处理程序。如下示例使用变量 done 来实现这一目的。

    CREATE PROCEDURE proc2()
    BEGIN
    DECLARE n INT DEFAULT 10;
    DECLARE done INT DEFAULT FALSE;
    retry_lable:
    REPEAT
    BEGIN
    DECLARE CONTINUE HANDLER FOR SQLWARNING
    BEGIN
    SET done = TRUE;
    END;
    IF done OR n < 0 THEN
    LEAVE retry_lable;
    END IF;
    SET n = n - 2;
    END;
    UNTIL FALSE END REPEAT;
    END;//
    Query OK, 0 rows affected