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 }
DECLARE ... HANDLER 语句指定了处理一个或多个条件的处理程序。语句可以是 SET var_name = value 这样的简单语句,也可以是使用 BEGIN 和 END 编写的复合语句。
注意:处理程序声明必须出现在变量或条件声明之后。
handler_action 值表示在执行处理程序语句后,处理程序将采取什么动作:
CONTINUE:继续执行当前程序。
EXIT:执行完声明的处理程序 BEGIN ... END 复合语句后终止程序。即使条件发生在内部程序块中,也是如此。
例如,有如下存储过程:
create procedure p_demo() BEGIN declare exit handler for SQLSTATE '23000' BEGIN select 'error message'; -- 该条语句会被执行 end; -- 这里会抛出主键唯一约束错误 select 'insert2'; -- 这条语句将不会执行 insert into t_demo value(1); select 'insert1'; insert into t_demo value(1); end
调用存储过程:
mysql> call p_demo(); +---------+ | insert1 | +---------+ | insert1 | +---------+ 1 row in set +---------------+ | error message | +---------------+ | error message | +---------------+ 1 row in set Query OK, 0 rows affected UNDO:不支持。
DECLARE ... HANDLER 的 condition_value 表示激活处理程序的特定条件或条件类别。它可以有以下几种形式:
mysql_error_code: 表示 MySQL 错误代码的整数文字,如 1051 表示 "未知表",示例:
DECLARE CONTINUE HANDLER FOR 1051 BEGIN -- body of handler END;
不要使用 MySQL 错误代码 0,因为它表示成功而不是错误状态。有关 MySQL 错误代码的列表,请参考 https://dev.mysql.com/doc/mysql-errors/5.7/en/server-error-reference.html。
SQLSTATE [VALUE] sqlstate_value: 表示 SQLSTATE 值的 5 个字符字符串,如 "42S01" 表示 "未知表",示例:
DECLARE CONTINUE HANDLER FOR SQLSTATE '42S02' BEGIN -- body of handler END;
请勿使用以 "00 "开头的 SQLSTATE 值,因为这些值表示成功而非错误状态。有关 SQLSTATE 值的列表,请参考 https://dev.mysql.com/doc/mysql-errors/5.7/en/server-error-reference.html。
condition_name:使用之前用 DECLARE ... CONDITION 语句指定的条件名称。条件名称可与 MySQL 错误代码或 SQLSTATE 值相关联。
SQLWARNING:以 "01" 开头的 SQLSTATE 值类别的简称。示例:
DECLARE CONTINUE HANDLER FOR SQLWARNING BEGIN -- body of handler END;
如果发生了未声明处理程序的情况,所采取的措施取决于条件类:
对于 SQLEXCEPTION 条件,存储程序会在引发条件的语句处终止,就像有一个 EXIT 处理程序一样。如果程序是由其他存储程序调用的,则调用程序会使用适用于自身处理程序的处理程序选择规则来处理该条件。
对于 SQLWARNING 条件,程序会继续执行,就像有一个 CONTINUE 处理程序一样。
对于 NOT FOUND 条件,如果条件是正常触发的,则执行 CONTINUE 操作。如果是通过 SIGNAL 或 RESIGNAL 引发的,则执行退出。
下面的示例使用了 SQLSTATE '23000' 处理程序,该处理程序用于处理重复键错误:
-- 创建一张表,拥有一个主键字段 mysql> CREATE TABLE test.t (s1 INT, PRIMARY KEY (s1)); Query OK, 0 rows affected (0.00 sec) -- 修改分隔符为 // mysql> delimiter // -- 创建存储过程 mysql> CREATE PROCEDURE handlerdemo () BEGIN -- 声明一个处理器 DECLARE CONTINUE HANDLER FOR SQLSTATE '23000' SET @x2 = 1; SET @x = 1; INSERT INTO test.t VALUES (1); SET @x = 2; INSERT INTO test.t VALUES (1); SET @x = 3; -- 这里依然会被执行,因为处理程序为 CONTINUE END; // Query OK, 0 rows affected (0.00 sec) -- 调用存储过程 mysql> CALL handlerdemo()// Query OK, 0 rows affected (0.00 sec) -- 查看结果 mysql> SELECT @x// +------+ | @x | +------+ | 3 | +------+ 1 row in set (0.00 sec)
请注意,在存储过程执行后,@x 为 3,这表明在错误发生后,存储过程的执行一直持续到结束。如果没有 DECLARE ... HANDLER 语句,MySQL 会在由于 PRIMARY KEY 约束导致第二次 INSERT 失败后采取默认操作(EXIT),而 SELECT @x 会返回 2。
要忽略某个条件,可为其声明一个 CONTINUE 处理程序,并将其与一个空代码块关联。例如:
DECLARE CONTINUE HANDLER FOR SQLWARNING BEGIN END;
其中,BEGIN END 就是一个空代码块。
代码块标签的范围不包括在代码块中声明的处理程序代码。因此,与处理程序相关的语句不能使用 ITERATE 或 LEAVE 来引用包含处理程序声明的块的标签。请看下面的示例,其中 REPEAT 块的标签是 retry:
CREATE PROCEDURE p () BEGIN DECLARE i INT DEFAULT 3; -- 为 REPEAT 代码块添加标签 retry: REPEAT BEGIN DECLARE CONTINUE HANDLER FOR SQLWARNING BEGIN -- 处理程序中引用了 REPEAT 代码块标签,标签不可见 ITERATE retry; # 非法 END; IF i < 0 THEN LEAVE retry; # 合法 END IF; SET i = i - 1; END; UNTIL FALSE END REPEAT; END;
retry 标签在程序块中 IF 语句的作用域内。但它不在 CONTINUE 处理程序的作用域内,因此此处的引用无效并导致错误:
ERROR 1308 (42000): LEAVE with no matching label: retry
要避免在处理程序中引用外部标签,请使用以下策略之一:
(1)如果要离开程序块,请使用 EXIT 处理程序。如果不需要清理程序块,BEGIN ... END 处理程序主体可以为空:
DECLARE EXIT HANDLER FOR SQLWARNING BEGIN END;
否则,将清理语句放在处理程序主体中:
DECLARE EXIT HANDLER FOR SQLWARNING BEGIN block cleanup statements END;
(2)如果要继续执行,可在 CONTINUE 处理程序中设置一个状态变量,在外层代码块中检查该状态变量,以确定是否调用了该处理程序。下面的示例为此使用了变量 done:
CREATE PROCEDURE p() BEGIN DECLARE i INT DEFAULT 3; -- 定义一个状态变量 DECLARE done INT DEFAULT FALSE; retry: REPEAT BEGIN DECLARE CONTINUE HANDLER FOR SQLWARNING BEGIN -- 当处理器被处罚后,修改状态变量 done 的值 SET done = TRUE; END; -- 判断状态变量 done IF done OR i < 0 THEN LEAVE retry; END IF; SET i = i - 1; END; UNTIL FALSE END REPEAT; END;