MySQL8-中文参考-六十三-

56 阅读56分钟

MySQL8 中文参考(六十三)

原文:docs.oracle.com/javase/tutorial/reallybigindex.html

15.5.1 准备语句

原文:dev.mysql.com/doc/refman/8.0/en/prepare.html

PREPARE *stmt_name* FROM *preparable_stmt*

PREPARE语句准备一个 SQL 语句,并分配一个名称*stmt_name*,以便以后引用该语句。准备好的语句通过EXECUTE执行,并通过DEALLOCATE PREPARE释放。有关示例,请参见第 15.5 节,“准备语句”。

语句名称不区分大小写。*preparable_stmt*可以是字符串文字或包含 SQL 语句文本的用户变量。文本必须表示单个语句,而不是多个语句。在语句内部,?字符可用作参数标记,指示稍后在执行时要将数据值绑定到查询的位置。?字符不应该被引号括起,即使您打算将它们绑定到字符串值。参数标记只能用于数据值应出现的地方,而不能用于 SQL 关键字、标识符等。

如果具有给定名称的准备语句已经存在,则在准备新语句之前会隐式释放它。这意味着如果新语句包含错误且无法准备,则会返回错误,并且不存在具有给定名称的语句。

准备语句的范围是创建它的会话,这有几个含义:

  • 在一个会话中创建的准备语句对其他会话不可用。

  • 当会话结束时,无论是正常结束还是异常结束,其准备的语句都不再存在。如果启用了自动重新连接,则客户端不会收到连接丢失的通知。因此,客户端可能希望禁用自动重新连接。请参见自动重新连接控制。

  • 在存储程序中创建的准备语句在程序执行完成后继续存在,并且可以在程序外部稍后执行。

  • 在存储程序上下文中准备的语句不能引用存储过程或函数参数或局部变量,因为它们在程序结束时超出范围,如果稍后在程序外执行该语句,则将无法使用。作为解决方法,可以引用会话范围的用户定义变量,参见第 11.4 节,“用户定义变量”。

从 MySQL 8.0.22 开始,用于准备语句的参数在语句首次准备时确定其类型,并且在为该准备语句调用EXECUTE时保留此类型(除非语句被重新准备,如本节后面所述)。确定参数类型的规则如下:

  • 作为二元算术运算符的操作数的参数具有与另一个操作数相同的数据类型。

  • 如果二元算术运算符的两个操作数都是参数,则参数的类型由运算符的上下文决定。

  • 如果参数是一元算术运算符的操作数,则参数的类型由运算符的上下文决定。

  • 如果算术运算符没有类型确定的上下文,则涉及的任何参数的派生类型为DOUBLE PRECISION。例如,当参数是SELECT列表中的顶层节点时,或者当它是比较运算符的一部分时,就会发生这种情况。

  • 作为字符串运算符的操作数的参数具有与其他操作数的聚合类型相同的派生类型。如果运算符的所有操作数都是参数,则派生类型为VARCHAR;其排序规则由collation_connection的值确定。

  • 作为时间运算符的操作数具有类型DATETIME,如果运算符返回DATETIME,则操作数的类型为TIME,如果运算符返回TIME,则操作数的类型为DATE,如果运算符返回DATE

  • 二元比较运算符的操作数具有与比较的另一个操作数相同的派生类型。

  • 作为BETWEEN等三元比较运算符的操作数的参数具有与其他操作数的聚合类型相同的派生类型。

  • 如果比较运算符的所有操作数都是参数,则它们每个的派生类型都是VARCHAR,其排序规则由collation_connection的值确定。

  • 作为CASECOALESCEIFIFNULLNULLIF的输出操作数之一的参数具有与运算符的其他输出操作数的聚合类型相同的派生类型。

  • 如果任何CASECOALESCEIFIFNULLNULLIF的所有输出操作数都是参数,或它们都是NULL,则参数的类型由运算符的上下文决定。

  • 如果参数是任何CASECOALESCE()IFIFNULL的操作数,并且没有类型确定的上下文,则涉及的每个参数的派生类型都是VARCHAR,其排序规则由collation_connection的值确定。

  • 作为CAST()的操作数的参数具有与CAST()指定的类型相同的类型。

  • 如果参数是不是INSERT语句的一部分的SELECT列表的直接成员,则参数的派生类型是VARCHAR,其排序规则由collation_connection的值确定。

  • 如果参数是INSERT](insert.html "15.2.7 INSERT Statement")语句的一部分的SELECT列表的直接成员,则参数的派生类型是将参数插入的相应列的类型。

  • 如果参数用作UPDATE语句的SET子句中的赋值的源,或者用作INSERT语句的ON DUPLICATE KEY UPDATE子句中的赋值的源,则参数的派生类型是由SETON DUPLICATE KEY UPDATE子句更新的相应列的类型。

  • 如果参数是函数的参数,则派生类型取决于函数的返回类型。

对于实际类型和派生类型的某些组合,会触发语句的自动重新准备,以确保与 MySQL 先前版本更紧密的兼容性。如果以下任一条件为真,则不会发生重新准备:

  • 使用NULL作为实际参数值。

  • 参数是CAST()的操作数。(相反,会尝试将其转换为派生类型,如果转换失败则引发异常。)

  • 参数是字符串。(在这种情况下,会执行隐式的CAST(? AS *derived_type*)。)

  • 参数的派生类型和实际类型都是INTEGER,并且符号相同。

  • 参数的派生类型是DECIMAL,实际类型是DECIMALINTEGER

  • 派生类型是DOUBLE,实际类型是任何数值类型。

  • 派生类型和实际类型都是字符串类型。

  • 如果派生类型是时间型,实际类型也是时间型。异常情况:派生类型是TIME,实际类型不是TIME;派生类型是DATE,实际类型不是DATE

  • 派生类型是时间型,实际类型是数值型。

对于除上述情况之外的情况,语句将重新准备,并使用实际参数类型而不是派生参数类型。

这些规则也适用于准备语句中引用的用户变量。

在准备语句中为给定参数或用户变量使用不同的数据类型,导致语句需要重新准备。这样效率较低;也可能导致参数(或变量)的实际类型变化,从而导致后续执行准备语句时结果不一致。因此,建议在重新执行准备语句时为给定参数使用相同的数据类型。

15.5.2 执行语句

原文:dev.mysql.com/doc/refman/8.0/en/execute.html

EXECUTE *stmt_name*
    [USING @*var_name* [, @*var_name*] ...]

在使用 PREPARE 准备语句之后,您可以使用一个 EXECUTE 语句来执行它,该语句引用了准备好的语句名称。如果准备的语句包含任何参数标记,您必须提供一个 USING 子句,列出包含要绑定到参数的值的用户变量。参数值只能由用户变量提供,并且 USING 子句必须命名与语句中参数标记数量完全相同的变量。

您可以多次执行给定的准备语句,向其传递不同的变量或在每次执行之前设置变量为不同的值。

有关示例,请参见 Section 15.5, “Prepared Statements”。

15.5.3 释放准备语句

原文:dev.mysql.com/doc/refman/8.0/en/deallocate-prepare.html

{DEALLOCATE | DROP} PREPARE *stmt_name*

要释放使用PREPARE生成的准备语句,使用一个引用准备语句名称的DEALLOCATE PREPARE语句。在释放准备语句后尝试执行它会导致错误。如果创建了太多准备语句,并且没有通过DEALLOCATE PREPARE语句或会话结束释放,您可能会遇到由max_prepared_stmt_count系统变量强制执行的上限。

有关示例,请参见第 15.5 节,“准备语句”。

15.6 复合语句语法

原文:dev.mysql.com/doc/refman/8.0/en/sql-compound-statements.html

15.6.1 BEGIN ... END 复合语句

15.6.2 语句标签

15.6.3 DECLARE 语句

15.6.4 存储程序中的变量

15.6.5 流程控制语句

15.6.6 游标

15.6.7 条件处理

15.6.8 条件处理的限制

本节描述了BEGIN ... END复合语句和其他可以在存储程序主体中使用的语句的语法:存储过程和函数、触发器和事件。这些对象是以 SQL 代码的形式定义的,存储在服务器上以供以后调用(参见第二十七章,存储对象)。

复合语句是一个可以包含其他块的块;变量声明、条件处理程序和游标;以及循环和条件测试等流程控制结构。

15.6.1 BEGIN ... END Compound Statement

原文:dev.mysql.com/doc/refman/8.0/en/begin-end.html

[*begin_label*:] BEGIN
    [*statement_list*]
END [*end_label*]

BEGIN ... END语法用于编写复合语句,这些语句可以出现在存储程序(存储过程和函数、触发器和事件)中。复合语句可以包含多个语句,由BEGINEND关键字括起来。*statement_list表示一个或多个语句的列表,每个语句以分号(;)语句分隔符结尾。statement_list*本身是可选的,因此空复合语句(BEGIN END)是合法的。

BEGIN ... END块可以嵌套。

使用多个语句需要客户端能够发送包含语句分隔符的语句字符串。在mysql命令行客户端中,可以使用delimiter命令处理这个问题。更改语句结束分隔符(例如,改为//)允许在程序体中使用。例如,请参阅 Section 27.1, “Defining Stored Programs”。

可以为BEGIN ... END块加标签。请参阅 Section 15.6.2, “Statement Labels”。

不支持可选的[NOT] ATOMIC子句。这意味着在指令块开始时不设置事务保存点,并且在此上下文中使用的BEGIN子句对当前事务没有影响。

注意

在所有存储程序中,解析器将BEGIN [WORK]视为BEGIN ... END块的开始。在这种情况下开始事务,请使用START TRANSACTION

15.6.2 语句标签

原文:dev.mysql.com/doc/refman/8.0/en/statement-labels.html

[*begin_label*:] BEGIN
    [*statement_list*]
END [*end_label*]

[*begin_label*:] LOOP
    *statement_list*
END LOOP [*end_label*]

[*begin_label*:] REPEAT
    *statement_list*
UNTIL *search_condition*
END REPEAT [*end_label*]

[*begin_label*:] WHILE *search_condition* DO
    *statement_list*
END WHILE [*end_label*]

标签允许用于 BEGIN ... END 块以及 LOOPREPEATWHILE 语句。这些语句的标签使用遵循以下规则:

  • begin_label 后必须跟着一个冒号。

  • begin_label 可以单独出现,不需要 end_label。如果有 end_label,它必须与 begin_label 相同。

  • end_label 不能单独出现,必须有 begin_label

  • 同一嵌套级别的标签必须是不同的。

  • 标签最多可以有 16 个字符长。

要引用标记结构内的标签,请使用 ITERATELEAVE 语句。以下示例使用这些语句来继续迭代或终止循环:

CREATE PROCEDURE doiterate(p1 INT)
BEGIN
  label1: LOOP
    SET p1 = p1 + 1;
    IF p1 < 10 THEN ITERATE label1; END IF;
    LEAVE label1;
  END LOOP label1;
END;

块标签的范围不包括在块内声明的处理程序的代码。有关详细信息,请参见 第 15.6.7.2 节,“DECLARE ... HANDLER Statement”。

15.6.3 声明语句

原文:dev.mysql.com/doc/refman/8.0/en/declare.html

DECLARE 语句用于定义程序内部的各种项目:

  • 局部变量。参见 第 15.6.4 节,“存储程序中的变量”。

  • 条件和处理程序。参见 第 15.6.7 节,“条件处理”。

  • 游标。参见 第 15.6.6 节,“游标”。

DECLARE 只允许在 BEGIN ... END 复合语句内部,并且必须位于其开始位置,在任何其他语句之前。

声明必须遵循一定的顺序。游标声明必须出现在处理程序声明之前。变量和条件声明必须出现在游标或处理程序声明之前。

15.6.4 存储程序中的变量

原文:dev.mysql.com/doc/refman/8.0/en/stored-program-variables.html

15.6.4.1 Local Variable DECLARE Statement

15.6.4.2 本地变量作用域和解析

系统变量和用户定义变量可以在存储程序中使用,就像在存储程序上下文之外使用一样。此外,存储程序可以使用DECLARE来定义本地变量,并且存储例程(过程和函数)可以声明接受参数,以在例程和其调用者之间传递值。

  • 要声明本地变量,请使用DECLARE语句,如 Section 15.6.4.1, “Local Variable DECLARE Statement”中所述。

  • 变量可以直接使用SET语句进行设置。参见 Section 15.7.6.1, “SET Syntax for Variable Assignment”。

  • 查询结果可以使用SELECT ... INTO *var_list*将其检索到本地变量中,或者通过打开游标并使用FETCH ... INTO *var_list*来实现。参见 Section 15.2.13.1, “SELECT ... INTO Statement”,以及 Section 15.6.6, “Cursors”。

有关本地变量的作用域以及 MySQL 如何解析模糊名称的信息,请参见 Section 15.6.4.2, “Local Variable Scope and Resolution”。

不允许将值DEFAULT分配给存储过程或函数参数或存储程序本地变量(例如使用SET *var_name* = DEFAULT语句)。在 MySQL 8.0 中,这将导致语法错误。

原文:dev.mysql.com/doc/refman/8.0/en/declare-local-variable.html

15.6.4.1 局部变量 DECLARE 语句

DECLARE *var_name* [, *var_name*] ... *type* [DEFAULT *value*]

此语句在存储程序中声明局部变量。要为变量提供默认值,请包含DEFAULT子句。该值可以指定为表达式;它不必是常量。如果缺少DEFAULT子句,则初始值为NULL

局部变量在数据类型和溢出检查方面与存储过程参数类似。请参阅第 15.1.17 节,“CREATE PROCEDURE 和 CREATE FUNCTION 语句”。

变量声明必须出现在游标或处理程序声明之前。

局部变量名称不区分大小写。允许的字符和引用规则与其他标识符相同,如第 11.2 节,“模式对象名称”中所述。

局部变量的作用域是其声明的BEGIN ... END块。该变量可以在声明块内嵌套的块中引用,除了那些声明具有相同名称变量的块。

有关变量声明的示例,请参阅第 15.6.4.2 节,“局部变量作用域和解析”。

原文:dev.mysql.com/doc/refman/8.0/en/local-variable-scope.html

15.6.4.2 本地变量作用域和解析

本地变量的作用域是其声明的 BEGIN ... END 块。该变量可以在声明块内嵌套的块中引用,除了那些声明具有相同名称变量的块。

因为本地变量仅在存储过程执行期间处于作用域内,所以不允许在存储过程内创建的准备语句中引用它们。准备语句的作用域是当前会话,而不是存储过程,因此该语句可能在程序结束后执行,此时变量将不再处于作用域内。例如,SELECT ... INTO *local_var* 不能作为准备语句使用。此限制也适用于存储过程和函数参数。参见 Section 15.5.1, “PREPARE Statement”。

本地变量不应与表列具有相同的名称。如果 SQL 语句(例如 SELECT ... INTO 语句)包含对具有相同名称的列和声明的本地变量的引用,MySQL 目前将该引用解释为变量的名称。考虑以下过程定义:

CREATE PROCEDURE sp1 (x VARCHAR(5))
BEGIN
  DECLARE xname VARCHAR(5) DEFAULT 'bob';
  DECLARE newname VARCHAR(5);
  DECLARE xid INT;

  SELECT xname, id INTO newname, xid
    FROM table1 WHERE xname = xname;
  SELECT newname;
END;

MySQL 在 SELECT 语句中将 xname 解释为 xname 变量的引用,而不是 xname 的引用。因此,当调用过程 sp1() 时,newname 变量返回值 'bob',而不管 table1.xname 列的值如何。

类似地,以下过程中的游标定义包含一个引用 xnameSELECT 语句。MySQL 将其解释为对该名称变量的引用,而不是列的引用。

CREATE PROCEDURE sp2 (x VARCHAR(5))
BEGIN
  DECLARE xname VARCHAR(5) DEFAULT 'bob';
  DECLARE newname VARCHAR(5);
  DECLARE xid INT;
  DECLARE done TINYINT DEFAULT 0;
  DECLARE cur1 CURSOR FOR SELECT xname, id FROM table1;
  DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;

  OPEN cur1;
  read_loop: LOOP
    FETCH FROM cur1 INTO newname, xid;
    IF done THEN LEAVE read_loop; END IF;
    SELECT newname;
  END LOOP;
  CLOSE cur1;
END;

另请参见 Section 27.8, “Restrictions on Stored Programs”。

15.6.5 流程控制语句

原文:dev.mysql.com/doc/refman/8.0/en/flow-control-statements.html

15.6.5.1 CASE 语句

15.6.5.2 IF 语句

15.6.5.3 ITERATE 语句

15.6.5.4 LEAVE 语句

15.6.5.5 LOOP 语句

15.6.5.6 REPEAT 语句

15.6.5.7 RETURN 语句

15.6.5.8 WHILE 语句

MySQL 支持 IFCASEITERATELEAVELOOPWHILEREPEAT 结构用于存储过程中的流程控制。它还支持存储函数中的 RETURN

许多这些结构包含其他语句,如下一节中的语法规范所示。这些结构可以嵌套。例如,一个 IF 语句可能包含一个 WHILE 循环,而该循环本身包含一个 CASE 语句。

MySQL 不支持 FOR 循环。

原文:dev.mysql.com/doc/refman/8.0/en/case.html

15.6.5.1 CASE Statement

CASE *case_value*
    WHEN *when_value* THEN *statement_list*
    [WHEN *when_value* THEN *statement_list*] ...
    [ELSE *statement_list*]
END CASE

或:

CASE
    WHEN *search_condition* THEN *statement_list*
    [WHEN *search_condition* THEN *statement_list*] ...
    [ELSE *statement_list*]
END CASE

存储程序的CASE语句实现了一个复杂的条件构造。

注意

还有一个与此处描述的CASE 语句不同的CASE 运算符。请参阅 Section 14.5, “Flow Control Functions”。CASE语句不能有ELSE NULL子句,并且以END CASE而不是END结束。

对于第一种语法,case_value是一个表达式。将此值与每个WHEN子句中的when_value表达式进行比较,直到找到相等的一个为止。找到相等的when_value后,执行相应的THEN子句statement_list。如果没有相等的*when_value,则执行ELSE子句statement_list*,如果有的话。

此语法不能用于与NULL进行相等性测试,因为NULL = NULL是错误的。请参阅 Section 5.3.4.6, “Working with NULL Values”。

对于第二种语法,每个WHEN子句*search_condition表达式会被评估,直到其中一个为真,此时执行相应的THEN子句statement_list。如果没有相等的search_condition,则执行ELSE子句statement_list*,如果有的话。

如果没有匹配被测试值的*when_valuesearch_condition*,并且CASE语句不包含ELSE子句,则会出现“CASE 语句未找到”错误。

每个*statement_list由一个或多个 SQL 语句组成;不允许为空的statement_list*。

为处理没有任何WHEN子句匹配的情况,请使用包含空的BEGIN ... END块的ELSE,如本示例所示。(此处ELSE子句中使用的缩进仅为了清晰起见,否则并不重要。)

DELIMITER |

CREATE PROCEDURE p()
  BEGIN
    DECLARE v INT DEFAULT 1;

    CASE v
      WHEN 2 THEN SELECT v;
      WHEN 3 THEN SELECT 0;
      ELSE
        BEGIN
        END;
    END CASE;
  END;
  |

原文:dev.mysql.com/doc/refman/8.0/en/if.html

15.6.5.2 IF Statement

IF *search_condition* THEN *statement_list*
    [ELSEIF *search_condition* THEN *statement_list*] ...
    [ELSE *statement_list*]
END IF

用于存储程序的IF语句实现了一个基本的条件构造。

注意

还有一个IF() 函数,它与此处描述的IF 语句不同。请参阅第 14.5 节,“流程控制函数”。IF语句可以有THENELSEELSEIF子句,并以END IF结束。

如果给定的*search_condition评估为 true,则相应的THENELSEIF子句statement_list执行。如果没有search_condition匹配,则ELSE子句statement_list*执行。

每个*statement_list由一个或多个 SQL 语句组成;不允许空的statement_list*。

一个IF ... END IF块,就像存储程序中使用的所有其他流程控制块一样,必须以分号结束,如本例所示:

DELIMITER //

CREATE FUNCTION SimpleCompare(n INT, m INT)
  RETURNS VARCHAR(20)

  BEGIN
    DECLARE s VARCHAR(20);

    IF n > m THEN SET s = '>';
    ELSEIF n = m THEN SET s = '=';
    ELSE SET s = '<';
    END IF;

    SET s = CONCAT(n, ' ', s, ' ', m);

    RETURN s;
  END //

DELIMITER ;

与其他流程控制结构一样,IF ... END IF块可以嵌套在其他流程控制结构中,包括其他IF语句。每个IF必须由其自己的END IF后跟一个分号来终止。您可以使用缩进使嵌套的流程控制块更容易被人类阅读(尽管 MySQL 不要求),如下所示:

DELIMITER //

CREATE FUNCTION VerboseCompare (n INT, m INT)
  RETURNS VARCHAR(50)

  BEGIN
    DECLARE s VARCHAR(50);

    IF n = m THEN SET s = 'equals';
    ELSE
      IF n > m THEN SET s = 'greater';
      ELSE SET s = 'less';
      END IF;

      SET s = CONCAT('is ', s, ' than');
    END IF;

    SET s = CONCAT(n, ' ', s, ' ', m, '.');

    RETURN s;
  END //

DELIMITER ;

在这个例子中,只有当n不等于m时,内部的IF才会被评估。

原文:dev.mysql.com/doc/refman/8.0/en/iterate.html

15.6.5.3 ITERATE Statement

ITERATE *label*

ITERATE 只能出现在 LOOPREPEATWHILE 语句内。ITERATE 意味着“重新开始循环”。

举例来说,参见第 15.6.5.5 节,“LOOP 语句”。

原文:dev.mysql.com/doc/refman/8.0/en/leave.html

15.6.5.4 LEAVE 语句

LEAVE *label*

此语句用于退出具有给定标签的流程控制结构。如果标签是最外层的存储程序块,LEAVE 将退出程序。

LEAVE 可以在 BEGIN ... END 或循环结构(LOOPREPEATWHILE)中使用。

例如,请参阅 Section 15.6.5.5, “LOOP Statement”。

原文:dev.mysql.com/doc/refman/8.0/en/loop.html

15.6.5.5 LOOP Statement

[*begin_label*:] LOOP
    *statement_list*
END LOOP [*end_label*]

LOOP 实现了一个简单的循环结构,允许对由一个或多个语句组成的语句列表重复执行,每个语句以分号 (;) 作为语句分隔符。循环内的语句将重复执行,直到循环被终止。通常,可以通过 LEAVE 语句来实现循环终止。在存储函数内部,也可以使用 RETURN 语句,该语句完全退出函数。

忽略包含循环终止语句会导致无限循环。

LOOP 语句可以被标记。有关标签使用规则,请参见 第 15.6.2 节,“语句标签”。

示例:

CREATE PROCEDURE doiterate(p1 INT)
BEGIN
  label1: LOOP
    SET p1 = p1 + 1;
    IF p1 < 10 THEN
      ITERATE label1;
    END IF;
    LEAVE label1;
  END LOOP label1;
  SET @x = p1;
END;

原文:dev.mysql.com/doc/refman/8.0/en/repeat.html

15.6.5.6 REPEAT Statement

[*begin_label*:] REPEAT
    *statement_list*
UNTIL *search_condition*
END REPEAT [*end_label*]

REPEAT语句中的语句列表将重复执行,直到*search_condition表达式为真。因此,REPEAT总是至少进入循环一次。statement_list*由一个或多个语句组成,每个语句以分号(;)作为语句分隔符。

一个REPEAT语句可以被标记。有关标签使用的规则,请参见第 15.6.2 节,“语句标签”。

示例:

mysql> delimiter //

mysql> CREATE PROCEDURE dorepeat(p1 INT)
       BEGIN
         SET @x = 0;
         REPEAT
           SET @x = @x + 1;
         UNTIL @x > p1 END REPEAT;
       END
       //
Query OK, 0 rows affected (0.00 sec)

mysql> CALL dorepeat(1000)//
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT @x//
+------+
| @x   |
+------+
| 1001 |
+------+
1 row in set (0.00 sec)

原文:dev.mysql.com/doc/refman/8.0/en/return.html

15.6.5.7 RETURN Statement

RETURN *expr*

RETURN 语句终止存储函数的执行,并将值 expr 返回给函数调用者。存储函数中必须至少有一个 RETURN 语句。如果函数有多个退出点,则可能有多个。

此语句不在存储过程、触发器或事件中使用。LEAVE 语句可用于退出这些类型的存储程序。

原文:dev.mysql.com/doc/refman/8.0/en/while.html

15.6.5.8 WHILE Statement

[*begin_label*:] WHILE *search_condition* DO
    *statement_list*
END WHILE [*end_label*]

WHILE语句中的语句列表会重复执行,只要*search_condition表达式为真。statement_list*由一个或多个 SQL 语句组成,每个语句以分号(;)作为语句分隔符。

一个带标签的WHILE语句。关于标签使用的规则,请参见第 15.6.2 节,“语句标签”。

示例:

CREATE PROCEDURE dowhile()
BEGIN
  DECLARE v1 INT DEFAULT 5;

  WHILE v1 > 0 DO
    ...
    SET v1 = v1 - 1;
  END WHILE;
END;

15.6.6 游标

原文:dev.mysql.com/doc/refman/8.0/en/cursors.html

15.6.6.1 游标关闭语句

15.6.6.2 游标声明语句

15.6.6.3 游标获取语句

15.6.6.4 游标打开语句

15.6.6.5 服务器端游标的限制

MySQL 支持存储程序内的游标。语法与嵌入式 SQL 相同。游标具有以下属性:

  • Asensitive:服务器可能会或可能不会复制其结果表

  • 只读:不可更新

  • Nonscrollable:只能单向遍历,不能跳过行

游标声明必须出现在处理程序声明之前,并且在变量和条件声明之后。

示例:

CREATE PROCEDURE curdemo()
BEGIN
  DECLARE done INT DEFAULT FALSE;
  DECLARE a CHAR(16);
  DECLARE b, c INT;
  DECLARE cur1 CURSOR FOR SELECT id,data FROM test.t1;
  DECLARE cur2 CURSOR FOR SELECT i FROM test.t2;
  DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;

  OPEN cur1;
  OPEN cur2;

  read_loop: LOOP
    FETCH cur1 INTO a, b;
    FETCH cur2 INTO c;
    IF done THEN
      LEAVE read_loop;
    END IF;
    IF b < c THEN
      INSERT INTO test.t3 VALUES (a,b);
    ELSE
      INSERT INTO test.t3 VALUES (a,c);
    END IF;
  END LOOP;

  CLOSE cur1;
  CLOSE cur2;
END;

原文:dev.mysql.com/doc/refman/8.0/en/close.html

15.6.6.1 游标关闭语句

CLOSE *cursor_name*

此语句关闭先前打开的游标。示例请参见第 15.6.6 节,“游标”。

如果游标未打开,则会发生错误。

如果未显式关闭,游标将在声明它的BEGIN ... END块结束时关闭。

原文:dev.mysql.com/doc/refman/8.0/en/declare-cursor.html

15.6.6.2 游标 DECLARE 语句

DECLARE *cursor_name* CURSOR FOR *select_statement*

此语句声明一个游标,并将其与检索游标要遍历的行的SELECT语句相关联。要稍后获取行,请使用FETCH语句。SELECT语句检索的列数必须与FETCH语句中指定的输出变量数相匹配。

SELECT语句不能有INTO子句。

游标声明必须出现在处理程序声明之前,并且在变量和条件声明之后。

一个存储程序可能包含多个游标声明,但在给定块中声明的每个游标必须具有唯一名称。例如,请参阅第 15.6.6 节,“游标”。

通过SHOW语句提供的信息,在许多情况下可以通过使用带有INFORMATION_SCHEMA表的游标获得等效信息。

原文:dev.mysql.com/doc/refman/8.0/en/fetch.html

15.6.6.3 Cursor FETCH Statement

FETCH [[NEXT] FROM] *cursor_name* INTO *var_name* [, *var_name*] ...

该语句获取与指定游标关联的 SELECT 语句的下一行,并移动游标指针。如果存在一行,则获取的列将存储在命名变量中。SELECT 语句检索的列数必须与 FETCH 语句中指定的输出变量数相匹配。

如果没有更多的行可用,则会发生一个 SQLSTATE 值为 '02000' 的 No Data 条件。要检测这种情况,您可以为其设置一个处理程序(或者为 NOT FOUND 条件)。例如,请参见 Section 15.6.6, “Cursors”。

请注意,另一个操作,例如 SELECT 或另一个 FETCH,也可能通过引发相同的条件来导致处理程序执行。如果需要区分哪个操作引发了条件,请将操作放置在其自己的 BEGIN ... END 块中,以便将其与自己的处理程序关联起来。

原文:dev.mysql.com/doc/refman/8.0/en/open.html

15.6.6.4 游标打开语句

OPEN *cursor_name*

这个语句打开了先前声明的游标。例如,请参阅第 15.6.6 节,“游标”。

原文:dev.mysql.com/doc/refman/8.0/en/cursor-restrictions.html

15.6.6.5 服务器端游标的限制

服务器端游标在 C API 中使用mysql_stmt_attr_set()函数实现。存储过程中的游标也使用相同的实现。服务器端游标使得可以在服务器端生成结果集,但除了客户端请求的行之外,不会将其传输到客户端。例如,如果客户端执行查询但只对第一行感兴趣,则不会传输其余行。

在 MySQL 中,服务器端游标被实例化为内部临时表。最初,这是一个MEMORY表,但当其大小超过max_heap_table_sizetmp_table_size系统变量的最小值时,将转换为MyISAM表。为了保存游标的结果集而创建的内部临时表与其他用途的内部临时表一样受到相同的限制。参见 Section 10.4.4, “Internal Temporary Table Use in MySQL”。实现的一个限制是,对于大型结果集,通过游标检索其行可能会很慢。

游标是只读的;不能使用游标更新行。

UPDATE WHERE CURRENT OFDELETE WHERE CURRENT OF未实现,因为不支持可更新游标。

游标不可保持(在提交后不保持打开状态)。

游标是不敏感的。

游标是不可滚动的。

游标没有名称。语句处理程序充当游标 ID。

每个准备语句只能打开一个游标。如果需要多个游标,必须准备多个语句。

如果不支持准备模式中的语句,则不能对生成结果集的语句使用游标。这包括CHECK TABLEHANDLER READSHOW BINLOG EVENTS等语句。

15.6.7 条件处理

原文:dev.mysql.com/doc/refman/8.0/en/condition-handling.html

15.6.7.1 声明...条件语句

15.6.7.2 声明...处理程序语句

15.6.7.3 获取诊断信息语句

15.6.7.4 重新发出语句

15.6.7.5 发出语句

15.6.7.6 处理程序的作用域规则

15.6.7.7 MySQL 诊断区域

15.6.7.8 条件处理和 OUT 或 INOUT 参数

在存储过程执行过程中可能会出现需要特殊处理的情况,比如退出当前程序块或继续执行。可以为一般条件(如警告或异常)或特定条件(如特定错误代码)定义处理程序。特定条件可以被赋予名称,并在处理程序中引用。

要命名条件,请使用DECLARE ... CONDITION语句。要声明处理程序,请使用DECLARE ... HANDLER语句。请参见第 15.6.7.1 节,“声明...条件语句”和第 15.6.7.2 节,“声明...处理程序语句”。有关服务器在条件发生时如何选择处理程序的信息,请参见第 15.6.7.6 节,“处理程序的作用域规则”。

要引发条件,请使用SIGNAL语句。要在条件处理程序中修改条件信息,请使用RESIGNAL。请参见第 15.6.7.1 节,“声明...条件语句”和第 15.6.7.2 节,“声明...处理程序语句”。

要从诊断区域检索信息,请使用GET DIAGNOSTICS语句(参见第 15.6.7.3 节,“获取诊断信息语句”)。有关诊断区域的信息,请参见第 15.6.7.7 节,“MySQL 诊断区域”。

原文:dev.mysql.com/doc/refman/8.0/en/declare-condition.html

15.6.7.1 DECLARE ... CONDITION Statement

DECLARE *condition_name* CONDITION FOR *condition_value*

*condition_value*: {
    *mysql_error_code*
  | SQLSTATE [VALUE] *sqlstate_value*
}

DECLARE ... CONDITION 语句声明了一个命名的错误条件,将一个名称与需要特定处理的条件关联起来。该名称可以在随后的 DECLARE ... HANDLER 语句中引用(参见 Section 15.6.7.2, “DECLARE ... HANDLER Statement”)。

条件声明必须出现在游标或处理程序声明之前。

DECLARE ... CONDITIONcondition_value 指示与条件名称关联的特定条件或条件类。它可以采用以下形式:

  • mysql_error_code: 表示 MySQL 错误代码的整数文字。

    不要使用 MySQL 错误代码 0,因为这表示成功而不是错误条件。有关 MySQL 错误代码的列表,请参见 Server Error Message Reference。

  • SQLSTATE [VALUE] sqlstate_value: 一个表示 SQLSTATE 值的 5 个字符的字符串文字。

    不要使用以 '00' 开头的 SQLSTATE 值,因为这些值表示成功而不是错误条件。有关 SQLSTATE 值的列表,请参见 Server Error Message Reference。

SIGNAL 或使用 RESIGNAL 语句中引用的条件名称必须与 SQLSTATE 值关联,而不是 MySQL 错误代码。

使用条件名称可以帮助使存储过程代码更清晰。例如,此处理程序适用于尝试删除不存在的表,但只有当您知道 1051 是 MySQL 错误代码“未知表”时才明显:

DECLARE CONTINUE HANDLER FOR 1051
  BEGIN
    -- body of handler
  END;

通过为条件声明一个名称,处理程序的目的更容易看到:

DECLARE no_such_table CONDITION FOR 1051;
DECLARE CONTINUE HANDLER FOR no_such_table
  BEGIN
    -- body of handler
  END;

这里有一个命名条件,与相同条件相对应,但基于相应的 SQLSTATE 值而不是 MySQL 错误代码:

DECLARE no_such_table CONDITION FOR SQLSTATE '42S02';
DECLARE CONTINUE HANDLER FOR no_such_table
  BEGIN
    -- body of handler
  END;

原文:dev.mysql.com/doc/refman/8.0/en/declare-handler.html

15.6.7.2 DECLARE ... HANDLER Statement

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 语句指定处理一个或多个条件的处理程序。如果其中一个条件发生,则执行指定的 statementstatement 可以是一个简单语句,如 SET *var_name* = *value*,或者使用 BEGINEND 编写的复合语句(参见 Section 15.6.1, “BEGIN ... END Compound Statement”)。

处理程序声明必须出现在变量或条件声明之后。

handler_action 值表示处理程序在执行处理程序语句后采取的操作:

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

  • EXIT:执行终止于声明处理程序的 BEGIN ... END 复合语句。即使条件发生在内部块中,这也是正确的。

  • UNDO:不支持。

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

  • mysql_error_code:表示 MySQL 错误代码的整数文字,例如 1051 表示“未知表”:

    DECLARE CONTINUE HANDLER FOR 1051
      BEGIN
        -- body of handler
      END;
    

    不要使用 MySQL 错误代码 0,因为这表示成功而不是错误条件。有关 MySQL 错误代码的列表,请参阅 Server Error Message Reference。

  • SQLSTATE [VALUE] sqlstate_value:表示 SQLSTATE 值的 5 个字符字符串文字,例如 '42S01' 表示“未知表”:

    DECLARE CONTINUE HANDLER FOR SQLSTATE '42S02'
      BEGIN
        -- body of handler
      END;
    

    不要使用以 '00' 开头的 SQLSTATE 值,因为这些值表示成功而不是错误条件。有关 SQLSTATE 值的列表,请参阅 Server Error Message Reference。

  • condition_name:先前使用 DECLARE ... CONDITION 指定的条件名。条件名可以与 MySQL 错误代码或 SQLSTATE 值关联。请参阅 Section 15.6.7.1, “DECLARE ... CONDITION Statement”。

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

    DECLARE CONTINUE HANDLER FOR SQLWARNING
      BEGIN
        -- body of handler
      END;
    
  • NOT FOUND:简写为以 '02' 开头的 SQLSTATE 值类别。在游标的上下文中相关,并用于控制当游标到达数据集末尾时发生的情况。如果没有更多行可用,则会发生无数据条件,其 SQLSTATE 值为 '02000'。要检测此条件,可以为其或为 NOT FOUND 条件设置处理程序。

    DECLARE CONTINUE HANDLER FOR NOT FOUND
      BEGIN
        -- body of handler
      END;
    

    举个例子,参见第 15.6.6 节“游标”。NOT FOUND条件也适用于检索不到行的SELECT ... INTO *var_list*语句。

  • SQLEXCEPTION:SQLSTATE 值的类别的简写,这些值不以'00''01''02'开头。

    DECLARE CONTINUE HANDLER FOR SQLEXCEPTION
      BEGIN
        -- body of handler
      END;
    

有关服务器在条件发生时选择处理程序的信息,请参见第 15.6.7.6 节“处理程序的作用域规则”。

如果发生未声明处理程序的条件,则采取的操作取决于条件类别:

  • 对于SQLEXCEPTION条件,存储过程在引发条件的语句处终止,就好像有一个EXIT处理程序一样。如果程序是由另一个存储过程调用的,则调用程序使用处理程序选择规则处理条件。

  • 对于SQLWARNING条件,程序会继续执行,就好像有一个CONTINUE处理程序一样。

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

以下示例使用了一个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;
       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)

注意,在过程执行后,@x3,这表明在错误发生后,执行继续到过程结束。如果DECLARE ... HANDLER语句不存在,MySQL 会在第二个由于PRIMARY KEY约束而导致的INSERT失败后采取默认操作(EXIT),SELECT @x将返回2

要忽略一个条件,为其声明一个CONTINUE处理程序,并将其与一个空块关联。例如:

DECLARE CONTINUE HANDLER FOR SQLWARNING BEGIN END;

块标签的作用域不包括在块内声明的处理程序的代码。因此,与处理程序关联的语句不能使用ITERATELEAVE来引用包围处理程序声明的块的标签。考虑以下示例,其中REPEAT块具有retry标签:

CREATE PROCEDURE p ()
BEGIN
  DECLARE i INT DEFAULT 3;
  retry:
    REPEAT
      BEGIN
        DECLARE CONTINUE HANDLER FOR SQLWARNING
          BEGIN
            ITERATE retry;    # illegal
          END;
        IF i < 0 THEN
          LEAVE retry;        # legal
        END IF;
        SET i = i - 1;
      END;
    UNTIL FALSE END REPEAT;
END;

retry标签在块内的IF语句中有效。它对于CONTINUE处理程序无效,因此那里的引用是无效的,会导致错误:

ERROR 1308 (42000): LEAVE with no matching label: retry

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

  • 要离开块,请使用EXIT处理程序。如果不需要块清理,BEGIN ... END处理程序主体可以为空:

    DECLARE EXIT HANDLER FOR SQLWARNING BEGIN END;
    

    否则,在处理程序主体中放置清理语句:

    DECLARE EXIT HANDLER FOR SQLWARNING
      BEGIN
        *block cleanup statements*
      END;
    
  • 要继续执行,请在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
                SET done = TRUE;
              END;
            IF done OR i < 0 THEN
              LEAVE retry;
            END IF;
            SET i = i - 1;
          END;
        UNTIL FALSE END REPEAT;
    END;
    

原文:dev.mysql.com/doc/refman/8.0/en/get-diagnostics.html

15.6.7.3 获取诊断信息语句

GET [CURRENT | STACKED] DIAGNOSTICS {
    *statement_information_item*
    [, *statement_information_item*] ...
  | CONDITION *condition_number*
    *condition_information_item*
    [, *condition_information_item*] ...
}

*statement_information_item*:
    *target* = *statement_information_item_name*

*condition_information_item*:
    *target* = *condition_information_item_name*

*statement_information_item_name*: {
    NUMBER
  | ROW_COUNT
}

*condition_information_item_name*: {
    CLASS_ORIGIN
  | SUBCLASS_ORIGIN
  | RETURNED_SQLSTATE
  | MESSAGE_TEXT
  | MYSQL_ERRNO
  | CONSTRAINT_CATALOG
  | CONSTRAINT_SCHEMA
  | CONSTRAINT_NAME
  | CATALOG_NAME
  | SCHEMA_NAME
  | TABLE_NAME
  | COLUMN_NAME
  | CURSOR_NAME
}

*condition_number*, *target*:
    (see following discussion)

SQL 语句生成填充诊断区域的诊断信息。获取诊断信息语句使应用程序能够检查这些信息。(您还可以使用显示警告显示错误来查看条件或错误。)

执行获取诊断信息不需要特殊权限。

关键字CURRENT表示从当前诊断区域检索信息。关键字STACKED表示从第二诊断区域检索信息,仅当当前上下文为条件处理程序时才可用。如果未给出任何关键字,则默认使用当前诊断区域。

获取诊断信息语句通常在存储程序内的处理程序中使用。这是 MySQL 的一个扩展,允许在处理程序上下文之外使用获取[当前]诊断信息来检查任何 SQL 语句的执行。例如,如果调用mysql客户端程序,则可以在提示符下输入这些语句:

mysql> DROP TABLE test.no_such_table;
ERROR 1051 (42S02): Unknown table 'test.no_such_table'
mysql> GET DIAGNOSTICS CONDITION 1
         @p1 = RETURNED_SQLSTATE, @p2 = MESSAGE_TEXT;
mysql> SELECT @p1, @p2;
+-------+------------------------------------+
| @p1   | @p2                                |
+-------+------------------------------------+
| 42S02 | Unknown table 'test.no_such_table' |
+-------+------------------------------------+

此扩展仅适用于当前诊断区域。它不适用于第二诊断区域,因为只有在当前上下文为条件处理程序时才允许使用获取堆叠诊断信息。如果不是这种情况,则会发生处理程序未激活时获取堆叠诊断信息错误。

有关诊断区域的描述,请参阅第 15.6.7.7 节,“MySQL 诊断区域”。简而言之,它包含两种信息:

  • 语句信息,如发生的条件数或受影响行数。

  • 条件信息,如错误代码和消息。如果语句引发多个条件,则诊断区域的此部分为每个条件区域。如果语句未引发任何条件,则诊断区域的此部分为空。

对于产生三个条件的语句,诊断区域包含如下语句和条件信息:

Statement information:
  row count
  ... other statement information items ...
Condition area list:
  Condition area 1:
    error code for condition 1
    error message for condition 1
    ... other condition information items ...
  Condition area 2:
    error code for condition 2:
    error message for condition 2
    ... other condition information items ...
  Condition area 3:
    error code for condition 3
    error message for condition 3
    ... other condition information items ...

获取诊断信息可以获取语句或条件信息,但不能在同一语句中同时获取两者:

  • 要获取语句信息,请将所需的语句项检索到目标变量中。此获取诊断信息实例将可用条件数和受影响行数分配给用户变量@p1@p2

    GET DIAGNOSTICS @p1 = NUMBER, @p2 = ROW_COUNT;
    
  • 要获取条件信息,请指定条件编号,并将所需的条件项检索到目标变量中。GET DIAGNOSTICS的这个实例将 SQLSTATE 值和错误消息分配给用户变量@p3@p4

    GET DIAGNOSTICS CONDITION 1
      @p3 = RETURNED_SQLSTATE, @p4 = MESSAGE_TEXT;
    

检索列表指定一个或多个*target* = *item_name*赋值,用逗号分隔。每个赋值命名一个目标变量,要么是*statement_information_item_name,要么是condition_information_item_name*标识符,取决于语句检索语句还是条件信息。

用于存储项目信息的有效*target*标识符可以是存储过程或函数参数,使用DECLARE声明的存储程序本地变量,或用户定义变量。

有效的*condition_number*标识符可以是存储过程或函数参数,使用DECLARE声明的存储程序本地变量,用户定义变量,系统变量或文字。如果条件编号不在具有信息的条件区域数量范围内,则会发出警告。在这种情况下,警告将添加到诊断区域而不清除它。

当发生条件时,MySQL 不会填充GET DIAGNOSTICS识别的所有条件项。例如:

mysql> GET DIAGNOSTICS CONDITION 1
         @p5 = SCHEMA_NAME, @p6 = TABLE_NAME;
mysql> SELECT @p5, @p6;
+------+------+
| @p5  | @p6  |
+------+------+
|      |      |
+------+------+

在标准 SQL 中,如果存在多个条件,则第一个条件与前一个 SQL 语句返回的SQLSTATE值相关。在 MySQL 中,这并不保证。要获取主要错误,不能这样做:

GET DIAGNOSTICS CONDITION 1 @errno = MYSQL_ERRNO;

相反,首先检索条件计数,然后使用它指定要检查的条件编号:

GET DIAGNOSTICS @cno = NUMBER;
GET DIAGNOSTICS CONDITION @cno @errno = MYSQL_ERRNO;

有关允许的语句和条件信息项以及在发生条件时哪些信息项被填充的信息,请参阅诊断区域信息项。

这是一个在存储过程上下文中使用GET DIAGNOSTICS和异常处理程序来评估插入操作结果的示例。如果插入成功,该过程使用GET DIAGNOSTICS获取受影响行数。这表明只要当前诊断区域未被清除,您可以多次使用GET DIAGNOSTICS来检索有关语句的信息。

CREATE PROCEDURE do_insert(value INT)
BEGIN
  -- Declare variables to hold diagnostics area information
  DECLARE code CHAR(5) DEFAULT '00000';
  DECLARE msg TEXT;
  DECLARE nrows INT;
  DECLARE result TEXT;
  -- Declare exception handler for failed insert
  DECLARE CONTINUE HANDLER FOR SQLEXCEPTION
    BEGIN
      GET DIAGNOSTICS CONDITION 1
        code = RETURNED_SQLSTATE, msg = MESSAGE_TEXT;
    END;

  -- Perform the insert
  INSERT INTO t1 (int_col) VALUES(value);
  -- Check whether the insert was successful
  IF code = '00000' THEN
    GET DIAGNOSTICS nrows = ROW_COUNT;
    SET result = CONCAT('insert succeeded, row count = ',nrows);
  ELSE
    SET result = CONCAT('insert failed, error = ',code,', message = ',msg);
  END IF;
  -- Say what happened
  SELECT result;
END;

假设t1.int_col是声明为NOT NULL的整数列。当调用该过程以插入非NULLNULL值时,该过程产生以下结果:

mysql> CALL do_insert(1);
+---------------------------------+
| result                          |
+---------------------------------+
| insert succeeded, row count = 1 |
+---------------------------------+

mysql> CALL do_insert(NULL);
+-------------------------------------------------------------------------+
| result                                                                  |
+-------------------------------------------------------------------------+
| insert failed, error = 23000, message = Column 'int_col' cannot be null |
+-------------------------------------------------------------------------+

当条件处理程序激活时,会发生对诊断区域堆栈的推送:

  • 第一个(当前)诊断区域变为第二个(堆叠)诊断区域,并创建一个新的当前诊断区域作为其副本。

  • GET [CURRENT] DIAGNOSTICSGET STACKED DIAGNOSTICS可以在处理程序内部使用,以访问当前诊断区域和堆叠诊断区域的内容。

  • 最初,两个诊断区域返回相同的结果,因此可以从当前诊断区域获取有关激活处理程序的条件的信息,只要在处理程序内不执行更改其当前诊断区域的语句。

  • 然而,在处理程序内执行的语句可以修改当前诊断区域,根据正常规则清除和设置其内容(参见诊断区域如何清除和填充)。

    获取有关激活处理程序条件的更可靠方法是使用堆叠的诊断区域,除了RESIGNAL之外,处理程序内执行的语句无法修改它。有关当前诊断区域何时设置和清除的信息,请参见第 15.6.7.7 节,“MySQL 诊断区域”。

下一个示例展示了如何在处理程序内部使用GET STACKED DIAGNOSTICS来获取有关已处理异常的信息,即使当前诊断区域已被处理程序语句修改。

在存储过程p()中,我们尝试向包含TEXT NOT NULL列的表中插入两个值。第一个值是非NULL字符串,第二个是NULL。该列禁止NULL值,因此第一个插入成功,但第二个导致异常。该过程包括一个异常处理程序,将尝试插入NULL映射为插入空字符串:

DROP TABLE IF EXISTS t1;
CREATE TABLE t1 (c1 TEXT NOT NULL);
DROP PROCEDURE IF EXISTS p;
delimiter //
CREATE PROCEDURE p ()
BEGIN
  -- Declare variables to hold diagnostics area information
  DECLARE errcount INT;
  DECLARE errno INT;
  DECLARE msg TEXT;
  DECLARE EXIT HANDLER FOR SQLEXCEPTION
  BEGIN
    -- Here the current DA is nonempty because no prior statements
    -- executing within the handler have cleared it
    GET CURRENT DIAGNOSTICS CONDITION 1
      errno = MYSQL_ERRNO, msg = MESSAGE_TEXT;
    SELECT 'current DA before mapped insert' AS op, errno, msg;
    GET STACKED DIAGNOSTICS CONDITION 1
      errno = MYSQL_ERRNO, msg = MESSAGE_TEXT;
    SELECT 'stacked DA before mapped insert' AS op, errno, msg;

    -- Map attempted NULL insert to empty string insert
    INSERT INTO t1 (c1) VALUES('');

    -- Here the current DA should be empty (if the INSERT succeeded),
    -- so check whether there are conditions before attempting to
    -- obtain condition information
    GET CURRENT DIAGNOSTICS errcount = NUMBER;
    IF errcount = 0
    THEN
      SELECT 'mapped insert succeeded, current DA is empty' AS op;
    ELSE
      GET CURRENT DIAGNOSTICS CONDITION 1
        errno = MYSQL_ERRNO, msg = MESSAGE_TEXT;
      SELECT 'current DA after mapped insert' AS op, errno, msg;
    END IF ;
    GET STACKED DIAGNOSTICS CONDITION 1
      errno = MYSQL_ERRNO, msg = MESSAGE_TEXT;
    SELECT 'stacked DA after mapped insert' AS op, errno, msg;
  END;
  INSERT INTO t1 (c1) VALUES('string 1');
  INSERT INTO t1 (c1) VALUES(NULL);
END;
//
delimiter ;
CALL p();
SELECT * FROM t1;

当处理程序激活时,当前诊断区域的副本被推送到诊断区域堆栈。处理程序首先显示当前诊断区域和堆叠诊断区域的内容,最初两者都相同:

+---------------------------------+-------+----------------------------+
| op                              | errno | msg                        |
+---------------------------------+-------+----------------------------+
| current DA before mapped insert |  1048 | Column 'c1' cannot be null |
+---------------------------------+-------+----------------------------+

+---------------------------------+-------+----------------------------+
| op                              | errno | msg                        |
+---------------------------------+-------+----------------------------+
| stacked DA before mapped insert |  1048 | Column 'c1' cannot be null |
+---------------------------------+-------+----------------------------+

GET DIAGNOSTICS语句之后执行的语句可能会重置当前诊断区域。例如,处理程序将NULL插入映射为空字符串插入并显示结果。新插入成功并清除当前诊断区域,但堆叠的诊断区域保持不变,仍然包含激活处理程序的条件信息:

+----------------------------------------------+
| op                                           |
+----------------------------------------------+
| mapped insert succeeded, current DA is empty |
+----------------------------------------------+

+--------------------------------+-------+----------------------------+
| op                             | errno | msg                        |
+--------------------------------+-------+----------------------------+
| stacked DA after mapped insert |  1048 | Column 'c1' cannot be null |
+--------------------------------+-------+----------------------------+

当条件处理程序结束时,其当前诊断区域从堆栈中弹出,堆叠的诊断区域成为存储过程中的当前诊断区域。

执行完该过程后,表中包含两行。空行是由于尝试插入NULL而映射到空字符串插入导致的:

+----------+
| c1       |
+----------+
| string 1 |
|          |
+----------+

在上面的示例中,在条件处理程序中的前两个GET DIAGNOSTICS语句中,从当前和堆叠的诊断区域检索信息的返回值相同。如果在处理程序内部较早执行重置当前诊断区域的语句,则情况就不同了。假设p()被重写为将DECLARE语句放在处理程序定义内部而不是在其之前:

CREATE PROCEDURE p ()
BEGIN
  DECLARE EXIT HANDLER FOR SQLEXCEPTION
  BEGIN
    -- Declare variables to hold diagnostics area information
    DECLARE errcount INT;
    DECLARE errno INT;
    DECLARE msg TEXT;
    GET CURRENT DIAGNOSTICS CONDITION 1
      errno = MYSQL_ERRNO, msg = MESSAGE_TEXT;
    SELECT 'current DA before mapped insert' AS op, errno, msg;
    GET STACKED DIAGNOSTICS CONDITION 1
      errno = MYSQL_ERRNO, msg = MESSAGE_TEXT;
    SELECT 'stacked DA before mapped insert' AS op, errno, msg;
...

在这种情况下,结果取决于版本:

  • 在 MySQL 5.7.2 之前,DECLARE不会更改当前诊断区域,因此前两个GET DIAGNOSTICS语句返回相同的结果,就像在p()的原始版本中一样。

    在 MySQL 5.7.2 中,已经做了工作以确保所有非诊断语句填充诊断区域,符合 SQL 标准。DECLARE是其中之一,因此在 5.7.2 及更高版本中,执行处理程序开头的DECLARE语句会清除当前诊断区域,并且GET DIAGNOSTICS语句会产生不同的结果:

    +---------------------------------+-------+------+
    | op                              | errno | msg  |
    +---------------------------------+-------+------+
    | current DA before mapped insert |  NULL | NULL |
    +---------------------------------+-------+------+
    
    +---------------------------------+-------+----------------------------+
    | op                              | errno | msg                        |
    +---------------------------------+-------+----------------------------+
    | stacked DA before mapped insert |  1048 | Column 'c1' cannot be null |
    +---------------------------------+-------+----------------------------+
    

在条件处理程序中避免此问题时,当试图获取激活处理程序的条件的信息时,请确保访问堆叠的诊断区域,而不是当前诊断区域。

原文:dev.mysql.com/doc/refman/8.0/en/resignal.html

15.6.7.4 RESIGNAL 语句

RESIGNAL [*condition_value*]
    [SET *signal_information_item*
    [, *signal_information_item*] ...]

*condition_value*: {
    SQLSTATE [VALUE] *sqlstate_value*
  | *condition_name*
}

*signal_information_item*:
    *condition_information_item_name* = *simple_value_specification*

*condition_information_item_name*: {
    CLASS_ORIGIN
  | SUBCLASS_ORIGIN
  | MESSAGE_TEXT
  | MYSQL_ERRNO
  | CONSTRAINT_CATALOG
  | CONSTRAINT_SCHEMA
  | CONSTRAINT_NAME
  | CATALOG_NAME
  | SCHEMA_NAME
  | TABLE_NAME
  | COLUMN_NAME
  | CURSOR_NAME
}

*condition_name*, *simple_value_specification*:
    (see following discussion)

RESIGNAL 传递在存储过程、函数、触发器或事件内部的复合语句中执行条件处理程序期间可用的错误条件信息。RESIGNAL 可能在传递信息之前更改部分或全部信息。RESIGNALSIGNAL 相关,但与 SIGNAL 不同,RESIGNAL 转发现有的条件信息,可能在修改后传递。

RESIGNAL 使得处理错误并返回错误信息成为可能。否则,在处理程序内执行 SQL 语句时,导致处理程序激活的信息将被销毁。RESIGNAL 还可以使一些过程变得更短,如果给定的处理程序可以处理部分情况,然后将条件“传递给上一级”到另一个处理程序。

执行 RESIGNAL 语句不需要特权。

所有形式的 RESIGNAL 需要当前上下文为条件处理程序。否则,RESIGNAL 是非法的,会出现 RESIGNAL when handler not active 错误。

要从诊断区域检索信息,请使用 GET DIAGNOSTICS 语句(参见 Section 15.6.7.3, “GET DIAGNOSTICS Statement”)。有关诊断区域的信息,请参阅 Section 15.6.7.7, “The MySQL Diagnostics Area”。

  • RESIGNAL 概述

  • 单独使用 RESIGNAL

  • 带有新信号信息的 RESIGNAL

  • 带有条件值和可选新信号信息的 RESIGNAL

  • RESIGNAL 需要条件处理程序上下文

RESIGNAL 概述

对于*condition_valuesignal_information_itemRESIGNAL的定义和规则与SIGNAL相同。例如,condition_value*可以是SQLSTATE值,该值可以指示错误、警告或“未找到”。有关更多信息,请参阅第 15.6.7.5 节,“SIGNAL Statement”。

RESIGNAL语句接受*condition_value*和SET子句,两者都是可选的。这导致了几种可能的用法:

  • 单独的RESIGNAL

    RESIGNAL;
    
  • RESIGNAL带有新的信号信息:

    RESIGNAL SET *signal_information_item* [, *signal_information_item*] ...;
    
  • 带有条件值和可能的新信号信息的RESIGNAL

    RESIGNAL *condition_value*
        [SET *signal_information_item* [, *signal_information_item*] ...];
    

这些用例都会导致诊断和条件区域的更改:

  • 诊断区包含一个或多个条件区域。

  • 条件区域包含条件信息项,例如SQLSTATE值、MYSQL_ERRNOMESSAGE_TEXT

有一个诊断区堆栈。当处理程序控制时,它会将诊断区推送到堆栈顶部,因此在处理程序执行期间有两个诊断区:

  • 第一个(当前)诊断区,最初是最后一个诊断区的副本,但会被第一个改变当前诊断区的处理程序中的第一个语句覆盖。

  • 最后一个(堆叠的)诊断区,其中包含在处理程序控制之前设置的条件区域。

诊断区中条件区域的最大数量由max_error_count系统变量的值确定。请参阅诊断区相关系统变量。

单独的 RESIGNAL

简单的RESIGNAL单独表示“不做任何更改地传递错误”。它恢复最后的诊断区并将其设置为当前诊断区。也就是说,它“弹出”诊断区堆栈。

在捕获条件的条件处理程序中,RESIGNAL单独的一个用法是执行其他操作,然后在不改变原始条件信息的情况下继续传递(即在进入处理程序之前存在的信息)。

例子:

DROP TABLE IF EXISTS xx;
delimiter //
CREATE PROCEDURE p ()
BEGIN
  DECLARE EXIT HANDLER FOR SQLEXCEPTION
  BEGIN
    SET @error_count = @error_count + 1;
    IF @a = 0 THEN RESIGNAL; END IF;
  END;
  DROP TABLE xx;
END//
delimiter ;
SET @error_count = 0;
SET @a = 0;
CALL p();

假设DROP TABLE xx语句失败。诊断区堆栈如下:

DA 1\. ERROR 1051 (42S02): Unknown table 'xx'

然后执行进入EXIT处理程序。它开始通过将诊断区推送到堆栈顶部,现在看起来像这样:

DA 1\. ERROR 1051 (42S02): Unknown table 'xx'
DA 2\. ERROR 1051 (42S02): Unknown table 'xx'

此时,第一个(当前)和第二个(堆叠的)诊断区的内容相同。第一个诊断区可能会被在处理程序内随后执行的语句修改。

通常,过程语句会清除第一个诊断区。BEGIN是一个例外,它不清除,什么也不做。SET不是例外,它会清除,执行操作,并产生“成功”的结果。诊断区堆栈现在看��来像这样:

DA 1\. ERROR 0000 (00000): Successful operation
DA 2\. ERROR 1051 (42S02): Unknown table 'xx'

在这一点上,如果@a = 0RESIGNAL会弹出诊断区堆栈,现在看起来像这样:

DA 1\. ERROR 1051 (42S02): Unknown table 'xx'

这就是调用者看到的。

如果@a不等于 0,处理程序简单地结束,这意味着当前诊断区不再有用(已经被“处理”),因此可以丢弃它,导致堆叠的诊断区变成当前诊断区。诊断区堆栈看起来像这样:

DA 1\. ERROR 0000 (00000): Successful operation

细节使其看起来复杂,但最终结果非常有用:处理程序可以执行而不破坏导致处理程序激活的条件的信息。

带有新信号信息的 RESIGNAL

带有SET子句的RESIGNAL提供新的信号信息,因此该语句意味着“传递带有更改的错误”:

RESIGNAL SET *signal_information_item* [, *signal_information_item*] ...;

与单独使用RESIGNAL一样,其思想是弹出诊断区堆栈,以便原始信息被清除。与单独使用RESIGNAL不同的是,SET子句中指定的任何内容都会发生变化。

例子:

DROP TABLE IF EXISTS xx;
delimiter //
CREATE PROCEDURE p ()
BEGIN
  DECLARE EXIT HANDLER FOR SQLEXCEPTION
  BEGIN
    SET @error_count = @error_count + 1;
    IF @a = 0 THEN RESIGNAL SET MYSQL_ERRNO = 5; END IF;
  END;
  DROP TABLE xx;
END//
delimiter ;
SET @error_count = 0;
SET @a = 0;
CALL p();

从之前的讨论中记得,单独使用RESIGNAL会导致诊断区堆栈如下:

DA 1\. ERROR 1051 (42S02): Unknown table 'xx'

RESIGNAL SET MYSQL_ERRNO = 5语句导致了这个堆栈,这是调用者看到的:

DA 1\. ERROR 5 (42S02): Unknown table 'xx'

换句话说,它改变了错误编号,而不会改变其他任何东西。

RESIGNAL语句可以更改任何或所有信号信息项,使诊断区的第一个条件区域看起来完全不同。

带有条件值和可选新信号信息的 RESIGNAL

带有条件值的RESIGNAL意味着“将一个条件推入当前诊断区。”如果存在SET子句,它还会改变错误信息。

RESIGNAL *condition_value*
    [SET *signal_information_item* [, *signal_information_item*] ...];

这种形式的RESIGNAL恢复了最后的诊断区并将其作为当前诊断区。也就是说,它“弹出”了诊断区堆栈,这与简单使用RESIGNAL的效果相同。但是,它还会根据条件值或信号信息更改诊断区。

例子:

DROP TABLE IF EXISTS xx;
delimiter //
CREATE PROCEDURE p ()
BEGIN
  DECLARE EXIT HANDLER FOR SQLEXCEPTION
  BEGIN
    SET @error_count = @error_count + 1;
    IF @a = 0 THEN RESIGNAL SQLSTATE '45000' SET MYSQL_ERRNO=5; END IF;
  END;
  DROP TABLE xx;
END//
delimiter ;
SET @error_count = 0;
SET @a = 0;
SET @@max_error_count = 2;
CALL p();
SHOW ERRORS;

这与之前的例子类似,效果相同,只是如果发生RESIGNAL,则最终当前条件区域看起来不同。(条件增加而不是替换现有条件的原因是使用了条件值。)

RESIGNAL 语句包括一个条件值(SQLSTATE '45000'),因此它添加了一个新的条件区域,导致诊断区域堆栈如下所示:

DA 1\. (condition 2) ERROR 1051 (42S02): Unknown table 'xx'
      (condition 1) ERROR 5 (45000) Unknown table 'xx'

对于这个例子,CALL p()SHOW ERRORS 的结果是:

mysql> CALL p();
ERROR 5 (45000): Unknown table 'xx'
mysql> SHOW ERRORS;
+-------+------+----------------------------------+
| Level | Code | Message                          |
+-------+------+----------------------------------+
| Error | 1051 | Unknown table 'xx'               |
| Error |    5 | Unknown table 'xx'               |
+-------+------+----------------------------------+
RESIGNAL 需要条件处理程序上下文

所有形式的RESIGNAL都要求当前上下文是一个条件处理程序。否则,RESIGNAL是非法的,会出现RESIGNAL when handler not active错误。例如:

mysql> CREATE PROCEDURE p () RESIGNAL;
Query OK, 0 rows affected (0.00 sec)

mysql> CALL p();
ERROR 1645 (0K000): RESIGNAL when handler not active

这里是一个更加困难的例子:

delimiter //
CREATE FUNCTION f () RETURNS INT
BEGIN
  RESIGNAL;
  RETURN 5;
END//
CREATE PROCEDURE p ()
BEGIN
  DECLARE EXIT HANDLER FOR SQLEXCEPTION SET @a=f();
  SIGNAL SQLSTATE '55555';
END//
delimiter ;
CALL p();

RESIGNAL 发生在存储函数 f() 中。虽然 f() 本身是在 EXIT 处理程序的上下文中调用的,但在 f() 中的执行有其自己的上下文,这不是处理程序上下文。因此,在 f() 中的 RESIGNAL 导致“处理程序未激活”错误。

原文:dev.mysql.com/doc/refman/8.0/en/signal.html

15.6.7.5 信号语句

SIGNAL *condition_value*
    [SET *signal_information_item*
    [, *signal_information_item*] ...]

*condition_value*: {
    SQLSTATE [VALUE] *sqlstate_value*
  | *condition_name*
}

*signal_information_item*:
    *condition_information_item_name* = *simple_value_specification*

*condition_information_item_name*: {
    CLASS_ORIGIN
  | SUBCLASS_ORIGIN
  | MESSAGE_TEXT
  | MYSQL_ERRNO
  | CONSTRAINT_CATALOG
  | CONSTRAINT_SCHEMA
  | CONSTRAINT_NAME
  | CATALOG_NAME
  | SCHEMA_NAME
  | TABLE_NAME
  | COLUMN_NAME
  | CURSOR_NAME
}

*condition_name*, *simple_value_specification*:
    (see following discussion)

SIGNAL是“返回”错误的方法。SIGNAL向处理程序、应用程序的外部部分或客户端提供错误信息。此外,它还可以控制错误的特性(错误编号、SQLSTATE值、消息)。没有SIGNAL,就必须诉诸解决方法,例如故意引用一个不存在的表来导致例程返回错误。

执行SIGNAL语句不需要特权。

要从诊断区域检索信息,请使用GET DIAGNOSTICS语句(参见第 15.6.7.3 节,“GET DIAGNOSTICS 语句”)。有关诊断区域的信息,请参阅第 15.6.7.7 节,“MySQL 诊断区域”。

  • 信号概述

  • 信号条件信息项

  • 信号对处理程序、游标和语句的影响

信号概述

SIGNAL语句中的*condition_value表示要返回的错误值。它可以是一个SQLSTATE值(一个 5 个字符的字符串文字)或一个condition_name*,它引用先前使用DECLARE ... CONDITION定义的命名条件(参见第 15.6.7.1 节,“DECLARE ... CONDITION 语句”)。

一个SQLSTATE值可以指示错误、警告或“未找到”。该值的前两个字符表示其错误类别,如信号条件信息项中所讨论的那样。一些信号值会导致语句终止;请参阅信号对处理程序、游标和语句的影响。

SIGNAL语句的SQLSTATE值不应以'00'开头,因为这样的值表示成功,不适用于发出错误信号。无论SQLSTATE值是直接在SIGNAL语句中指定还是在语句中引用的命名条件中引用,都是如此。如果该值无效,则会发生Bad SQLSTATE错误。

要发出通用的SQLSTATE值,请使用'45000',表示“未处理的用户定义异常”。

SIGNAL语句可选地包含一个SET子句,其中包含多个信号项,以*condition_information_item_name* = *simple_value_specification*分配的列表,用逗号分隔。

每个*condition_information_item_name*在SET子句中只能指定一次。否则,会出现Duplicate condition information item错误。

可以使用存储过程或函数参数、使用DECLARE声明的存储程序本地变量、用户定义变量、系统变量或文字指定有效的*simple_value_specification标识符。字符文字可能包括一个_charset*引导符。

有关可接受的*condition_information_item_name*值的信息,请参阅 Signal Condition Information Items。

以下过程根据其输入参数pval的值发出错误或警告:

CREATE PROCEDURE p (pval INT)
BEGIN
  DECLARE specialty CONDITION FOR SQLSTATE '45000';
  IF pval = 0 THEN
    SIGNAL SQLSTATE '01000';
  ELSEIF pval = 1 THEN
    SIGNAL SQLSTATE '45000'
      SET MESSAGE_TEXT = 'An error occurred';
  ELSEIF pval = 2 THEN
    SIGNAL specialty
      SET MESSAGE_TEXT = 'An error occurred';
  ELSE
    SIGNAL SQLSTATE '01000'
      SET MESSAGE_TEXT = 'A warning occurred', MYSQL_ERRNO = 1000;
    SIGNAL SQLSTATE '45000'
      SET MESSAGE_TEXT = 'An error occurred', MYSQL_ERRNO = 1001;
  END IF;
END;

如果pval为 0,p()会发出一个警告,因为以'01'开头的SQLSTATE值属于警告类别。警告不会终止该过程,并且在过程返回后可以使用SHOW WARNINGS查看。

如果pval为 1,p()会发出一个错误并设置MESSAGE_TEXT条件信息项。错误会终止该过程,并且文本将随错误信息一起返回。

如果pval为 2,则会发出相同的错误,尽管在这种情况下使用命名条件指定了SQLSTATE值。

如果pval为其他任何值,p()首先发出一个警告并设置消息文本和错误编号条件信息项。此警告不会终止该过程,因此执行会继续,然后p()会发出一个错误。错误会终止该过程。警告设置的消息文本和错误编号将被错误设置的值替换,这些值将与错误信息一起返回。

SIGNAL通常在存储程序中使用,但是 MySQL 扩展允许在处理程序上下文之外使用。例如,如果调用mysql客户端程序,则可以在提示符下输入以下任何语句:

SIGNAL SQLSTATE '77777';

CREATE TRIGGER t_bi BEFORE INSERT ON t
  FOR EACH ROW SIGNAL SQLSTATE '77777';

CREATE EVENT e ON SCHEDULE EVERY 1 SECOND
  DO SIGNAL SQLSTATE '77777';

SIGNAL根据以下规则执行:

如果SIGNAL语句指示特定的SQLSTATE值,则该值用于发出指定的条件。例如:

CREATE PROCEDURE p (divisor INT)
BEGIN
  IF divisor = 0 THEN
    SIGNAL SQLSTATE '22012';
  END IF;
END;

如果 SIGNAL 语句使用了一个命名条件,该条件必须在适用于 SIGNAL 语句的某个范围内声明,并且必须使用 SQLSTATE 值而不是 MySQL 错误编号进行定义。示例:

CREATE PROCEDURE p (divisor INT)
BEGIN
  DECLARE divide_by_zero CONDITION FOR SQLSTATE '22012';
  IF divisor = 0 THEN
    SIGNAL divide_by_zero;
  END IF;
END;

如果在 SIGNAL 语句的范围内不存在命名条件,则会发生 Undefined CONDITION 错误。

如果 SIGNAL 引用了一个使用 MySQL 错误编号而不是 SQLSTATE 值定义的命名条件,则会发生 SIGNAL/RESIGNAL 只能使用使用 SQLSTATE 定义的 CONDITION 错误。以下语句会导致该错误,因为命名条件与 MySQL 错误编号相关联:

DECLARE no_such_table CONDITION FOR 1051;
SIGNAL no_such_table;

如果在不同范围内多次声明具有相同名称的条件,则具有最局部范围的声明适用。考虑以下过程:

CREATE PROCEDURE p (divisor INT)
BEGIN
  DECLARE my_error CONDITION FOR SQLSTATE '45000';
  IF divisor = 0 THEN
    BEGIN
      DECLARE my_error CONDITION FOR SQLSTATE '22012';
      SIGNAL my_error;
    END;
  END IF;
  SIGNAL my_error;
END;

如果 divisor 为 0,则执行第一个 SIGNAL 语句。最内层的 my_error 条件声明适用,引发 SQLSTATE '22012'

如果 divisor 不为 0,则执行第二个 SIGNAL 语句。最外层的 my_error 条件声明适用,引发 SQLSTATE '45000'

有关服务器在发生条件时选择处理程序的信息,请参阅 第 15.6.7.6 节,“处理程序的作用域规则”。

异常处理程序内部可以引发信号:

CREATE PROCEDURE p ()
BEGIN
  DECLARE EXIT HANDLER FOR SQLEXCEPTION
  BEGIN
    SIGNAL SQLSTATE VALUE '99999'
      SET MESSAGE_TEXT = 'An error occurred';
  END;
  DROP TABLE no_such_table;
END;

CALL p() 到达 DROP TABLE 语句。没有名为 no_such_table 的表,因此激活了错误处理程序。错误处理程序销毁原始错误(“没有这样的表”),并生成一个具有 SQLSTATE '99999' 和消息 An error occurred 的新错误。

信号条件信息项

以下表列出了可以在 SIGNAL(或 RESIGNAL)语句中设置的诊断区域条件信息项的名称。所有项目都是标准 SQL,除了 MYSQL_ERRNO,它是 MySQL 的扩展。有关这些项目的更多信息,请参阅 第 15.6.7.7 节,“MySQL 诊断区域”。

Item Name             Definition
---------             ----------
CLASS_ORIGIN          VARCHAR(64)
SUBCLASS_ORIGIN       VARCHAR(64)
CONSTRAINT_CATALOG    VARCHAR(64)
CONSTRAINT_SCHEMA     VARCHAR(64)
CONSTRAINT_NAME       VARCHAR(64)
CATALOG_NAME          VARCHAR(64)
SCHEMA_NAME           VARCHAR(64)
TABLE_NAME            VARCHAR(64)
COLUMN_NAME           VARCHAR(64)
CURSOR_NAME           VARCHAR(64)
MESSAGE_TEXT          VARCHAR(128)
MYSQL_ERRNO           SMALLINT UNSIGNED

字符项的字符集为 UTF-8。

SIGNAL 语句中将 NULL 赋给条件信息项是非法的。

SIGNAL 语句总是指定一个 SQLSTATE 值,可以直接指定,也可以间接引用具有 SQLSTATE 值的命名条件。SQLSTATE 值的前两个字符是其类别,类别确定条件信息项的默认值:

  • 类 = '00'(成功)

    非法。以'00'开头的SQLSTATE值表示成功,并且对于SIGNAL无效。

  • 类别 = '01'(警告)

    MESSAGE_TEXT = 'Unhandled user-defined warning condition';
    MYSQL_ERRNO = ER_SIGNAL_WARN
    
  • 类别 = '02'(未找到)

    MESSAGE_TEXT = 'Unhandled user-defined not found condition';
    MYSQL_ERRNO = ER_SIGNAL_NOT_FOUND
    
  • 类别 > '02'(异常)

    MESSAGE_TEXT = 'Unhandled user-defined exception condition';
    MYSQL_ERRNO = ER_SIGNAL_EXCEPTION
    

对于合法类别,其他条件信息项设置如下:

CLASS_ORIGIN = SUBCLASS_ORIGIN = '';
CONSTRAINT_CATALOG = CONSTRAINT_SCHEMA = CONSTRAINT_NAME = '';
CATALOG_NAME = SCHEMA_NAME = TABLE_NAME = COLUMN_NAME = '';
CURSOR_NAME = '';

SIGNAL执行后可访问的错误值是由SIGNAL语句引发的SQLSTATE值以及MESSAGE_TEXTMYSQL_ERRNO项。这些值可从 C API 中获取:

  • mysql_sqlstate()返回SQLSTATE值。

  • mysql_errno()返回MYSQL_ERRNO值。

  • mysql_error()返回MESSAGE_TEXT值。

在 SQL 级别,SHOW WARNINGSSHOW ERRORS的输出指示CodeMessage列中的MYSQL_ERRNOMESSAGE_TEXT值。

要从诊断区域检索信息,请使用GET DIAGNOSTICS语句(参见 Section 15.6.7.3, “GET DIAGNOSTICS Statement”)。有关诊断区域的信息,请参阅 Section 15.6.7.7, “The MySQL Diagnostics Area”。

信号对处理程序、游标和语句的影响

信号对语句执行的影响取决于信号类别。类别确定错误的严重程度。MySQL 忽略sql_mode系统变量的值;特别是,严格的 SQL 模式无关紧要。MySQL 还忽略IGNORESIGNAL的目的是明确引发用户生成的错误,因此信号永远不会被忽略。

在以下描述中,“未处理”表示未使用DECLARE ... HANDLER为信号的SQLSTATE值定义处理程序。

  • 类别 = '00'(成功)

    非法。以'00'开头的SQLSTATE值表示成功,并且对于SIGNAL无效。

  • 类别 = '01'(警告)

    warning_count系统变量的值增加。SHOW WARNINGS显示信号。SQLWARNING处理程序捕获信号。

    由于导致函数返回的RETURN语句清除了诊断区域,因此无法从存储函数中返回警告。该语句清除了可能存在的任何警告(并将warning_count重置为 0)。

  • 类别 = '02'(未找到)

    NOT FOUND处理程序捕获信号。对游标没有影响。如果在存储函数中未处理信号,则语句结束。

  • 类别 > '02'(异常)

    SQLEXCEPTION处理程序捕获信号。如果在存储函数中未处理信号,则语句结束。

  • 类别 = '40'

    被视为普通异常。

原文:dev.mysql.com/doc/refman/8.0/en/handler-scope.html

15.6.7.6 处理程序的范围规则

存储过程可以包括在程序内发生某些条件时调用的处理程序。每个处理程序的适用性取决于其在程序定义中的位置以及它处理的条件或条件:

  • BEGIN ... END块中声明的处理程序仅对在块中处理程序声明后的 SQL 语句有效。如果处理程序本身引发条件,则它无法处理该条件,也不能处理块中声明的任何其他处理程序。在下面的示例中,处理程序H1H2适用于*stmt1stmt2*语句引发的条件。但是H1H2对于在H1H2主体中引发的条件不适用。

    BEGIN -- outer block
      DECLARE EXIT HANDLER FOR ...;  -- handler H1
      DECLARE EXIT HANDLER FOR ...;  -- handler H2
      *stmt1*;
      *stmt2*;
    END;
    
  • 处理程序仅在声明它的块中有效,并且不能用于发生在该块外部的条件。在下面的示例中,处理程序H1仅在内部块中的*stmt1中有效,而不适用于外部块中的stmt2*:

    BEGIN -- outer block
      BEGIN -- inner block
        DECLARE EXIT HANDLER FOR ...;  -- handler H1
        *stmt1*;
      END;
      *stmt2*;
    END;
    
  • 处理程序可以是特定的或一般的。特定处理程序是针对 MySQL 错误代码、SQLSTATE值或条件名称的。一般处理程序是针对SQLWARNINGSQLEXCEPTIONNOT FOUND类中的条件。条件特异性与条件优先级有关,如后面所述。

多个处理程序可以在不同的范围和具有不同的特异性中声明。例如,在外部块中可能有一个特定的 MySQL 错误代码处理程序,而在内部块中可能有一个一般的SQLWARNING处理程序。或者在同一块中可能有一个特定的 MySQL 错误代码处理程序和一般的SQLWARNING类处理程序。

处理程序是否被激活不仅取决于其自身的范围和条件值,还取决于其他处理程序的存在。当存储过程中发生条件时,服务器会在当前范围(当前BEGIN ... END块)中搜索适用的处理程序。如果没有适用的处理程序,则搜索会继续向外进行,直到找到每个连续包含范围(块)中的处理程序。当服务器在给定范围找到一个或多个适用的处理程序时,它会根据条件优先级在它们之间进行选择:

  • 一个 MySQL 错误代码处理程序优先于一个SQLSTATE值处理程序。

  • 一个SQLSTATE值处理程序优先于一般的SQLWARNINGSQLEXCEPTIONNOT FOUND处理程序。

  • 一个SQLEXCEPTION处理程序优先于一个SQLWARNING处理程序。

  • 可能存在几个具有相同优先级的适用处理程序。例如,一个语句可能生成多个具有不同错误代码的警告,对于每个警告都存在一个特定错误的处理程序。在这种情况下,服务器激活哪个处理程序的选择是不确定的,并且可能根据条件发生的情况而变化。

处理程序选择规则的一个含义是,如果不同作用域中存在多个适用的处理程序,则具有最局部作用域的处理程序优先于外部作用域中的处理程序,甚至优先于更具体条件的处理程序。

如果在条件发生时没有适当的处理程序,则采取的操作取决于条件的类别:

  • 对于SQLEXCEPTION条件,存储程序在引发条件的语句处终止,就好像有一个EXIT处理程序。如果程序是由另一个存储程序调用的,则调用程序使用其自己的处理程序选择规则处理条件。

  • 对于SQLWARNING条件,程序继续执行,就好像有一个CONTINUE处理程序。

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

以下示例演示了 MySQL 如何应用处理程序选择规则。

这个过程包含两个处理程序,一个用于特定的SQLSTATE值('42S02'),用于尝试删除不存在表时发生的情况,另一个用于一般的SQLEXCEPTION类:

CREATE PROCEDURE p1()
BEGIN
  DECLARE CONTINUE HANDLER FOR SQLSTATE '42S02'
    SELECT 'SQLSTATE handler was activated' AS msg;
  DECLARE CONTINUE HANDLER FOR SQLEXCEPTION
    SELECT 'SQLEXCEPTION handler was activated' AS msg;

  DROP TABLE test.t;
END;

两个处理程序都在同一个块中声明并具有相同的作用域。然而,SQLSTATE处理程序优先于SQLEXCEPTION处理程序,因此如果表t不存在,则DROP TABLE语句引发一个激活SQLSTATE处理程序的条件:

mysql> CALL p1();
+--------------------------------+
| msg                            |
+--------------------------------+
| SQLSTATE handler was activated |
+--------------------------------+

这个过程包含相同的两个处理程序。但这次,DROP TABLE语句和SQLEXCEPTION处理程序在相对于SQLSTATE处理程序的内部块中:

CREATE PROCEDURE p2()
BEGIN -- outer block
    DECLARE CONTINUE HANDLER FOR SQLSTATE '42S02'
      SELECT 'SQLSTATE handler was activated' AS msg;
  BEGIN -- inner block
    DECLARE CONTINUE HANDLER FOR SQLEXCEPTION
      SELECT 'SQLEXCEPTION handler was activated' AS msg;

    DROP TABLE test.t; -- occurs within inner block
  END;
END;

在这种情况下,更接近条件发生位置的处理程序优先。即使SQLEXCEPTION处理程序比SQLSTATE处理程序更一般,SQLEXCEPTION处理程序也会被激活:

mysql> CALL p2();
+------------------------------------+
| msg                                |
+------------------------------------+
| SQLEXCEPTION handler was activated |
+------------------------------------+

在这个过程中,处理程序之一在DROP TABLE语句的作用域内部声明:

CREATE PROCEDURE p3()
BEGIN -- outer block
  DECLARE CONTINUE HANDLER FOR SQLEXCEPTION
    SELECT 'SQLEXCEPTION handler was activated' AS msg;
  BEGIN -- inner block
    DECLARE CONTINUE HANDLER FOR SQLSTATE '42S02'
      SELECT 'SQLSTATE handler was activated' AS msg;
  END;

  DROP TABLE test.t; -- occurs within outer block
END;

只有SQLEXCEPTION处理程序适用,因为另一个处理程序不适用于DROP TABLE引发的条件:

mysql> CALL p3();
+------------------------------------+
| msg                                |
+------------------------------------+
| SQLEXCEPTION handler was activated |
+------------------------------------+

在这个过程中,两个处理程序都在DROP TABLE语句的作用域内部声明:

CREATE PROCEDURE p4()
BEGIN -- outer block
  BEGIN -- inner block
    DECLARE CONTINUE HANDLER FOR SQLEXCEPTION
      SELECT 'SQLEXCEPTION handler was activated' AS msg;
    DECLARE CONTINUE HANDLER FOR SQLSTATE '42S02'
      SELECT 'SQLSTATE handler was activated' AS msg;
  END;

  DROP TABLE test.t; -- occurs within outer block
END;

由于它们不在DROP TABLE的范围内,因此都不适用。语句引发的条件未被处理,导致过程以错误终止:

mysql> CALL p4();
ERROR 1051 (42S02): Unknown table 'test.t'