文档主页
MySQL 8.4 参考手册
相关文档 下载本手册
PDF (US Ltr) - 39.9Mb
PDF (A4) - 40.0Mb
手册页 (TGZ) - 258.5Kb
手册页 (Zip) - 365.5Kb
信息 (Gzip) - 4.0Mb
信息 (Zip) - 4.0Mb


MySQL 8.4 参考手册  /  ...  /  CREATE PROCEDURE 和 CREATE FUNCTION 语句

15.1.17 CREATE PROCEDURE 和 CREATE FUNCTION 语句

CREATE
    [DEFINER = user]
    PROCEDURE [IF NOT EXISTS] sp_name ([proc_parameter[,...]])
    [characteristic ...] routine_body

CREATE
    [DEFINER = user]
    FUNCTION [IF NOT EXISTS] sp_name ([func_parameter[,...]])
    RETURNS type
    [characteristic ...] routine_body

proc_parameter:
    [ IN | OUT | INOUT ] param_name type

func_parameter:
    param_name type

type:
    Any valid MySQL data type

characteristic: {
    COMMENT 'string'
  | LANGUAGE SQL
  | [NOT] DETERMINISTIC
  | { CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA }
  | SQL SECURITY { DEFINER | INVOKER }
}

routine_body:
    SQL routine

这些语句用于创建存储例程(存储过程或函数)。也就是说,指定的例程将被服务器识别。默认情况下,存储例程与默认数据库相关联。要将例程显式关联到给定数据库,请在创建例程时将名称指定为 db_name.sp_name。

在 MySQL 中,CREATE FUNCTION 语句也用于支持可加载函数。请参阅 第 15.7.4.1 节 “CREATE FUNCTION 语句(用于可加载函数)”。可加载函数可以视为外部存储函数。存储函数与其命名空间中的可加载函数共享。请参阅 第 11.2.5 节 “函数名称解析和解析”,以了解描述服务器如何解释对不同类型函数的引用的规则。

要调用存储过程,请使用 CALL 语句(请参阅 第 15.2.1 节 “CALL 语句”)。要调用存储函数,请在表达式中引用它。函数在表达式求值期间返回一个值。

CREATE PROCEDURE 和 CREATE FUNCTION 需要 CREATE ROUTINE 权限。如果存在 DEFINER 子句,则所需的权限取决于 user 值,如 第 27.6 节“存储对象访问控制” 中所述。如果启用了二进制日志记录,CREATE FUNCTION 可能需要 SUPER 权限,如 第 27.7 节“存储程序二进制日志记录” 中所述。

默认情况下,MySQL 会自动向例程创建者授予 ALTER ROUTINE 和 EXECUTE 权限。可以通过禁用 automatic_sp_privileges 系统变量来更改此行为。请参阅 第 27.2.2 节“存储例程和 MySQL 权限”。

DEFINER 和 SQL SECURITY 子句指定在例程执行时检查访问权限时要使用的安全上下文,如本节稍后所述。

如果例程名称与内置 SQL 函数的名称相同,则会发生语法错误,除非在定义例程或稍后调用它时,在名称和后面的括号之间使用空格。因此,请避免对您自己的存储例程使用现有 SQL 函数的名称。

IGNORE_SPACE SQL 模式适用于内置函数,而不适用于存储例程。无论是否启用了 IGNORE_SPACE,始终允许在存储例程名称后使用空格。

IF NOT EXISTS 可防止在已存在具有相同名称的例程时发生错误。此选项在 CREATE FUNCTION 和 CREATE PROCEDURE 中均受支持。

如果已存在具有相同名称的内置函数,则尝试使用 CREATE FUNCTION ... IF NOT EXISTS 创建存储函数会成功,但会发出警告,指示它与本机函数同名;这与在不指定 IF NOT EXISTS 的情况下执行相同的 CREATE FUNCTION 语句时没有区别。

如果已存在具有相同名称的可加载函数,则尝试使用 IF NOT EXISTS 创建存储函数会成功,但会发出警告。这与不指定 IF NOT EXISTS 的情况相同。

有关详细信息,请参阅 函数名称解析。

括号内的参数列表必须始终存在。如果没有参数,则应使用空参数列表 ()。参数名称不区分大小写。

默认情况下,每个参数都是 IN 参数。要为参数指定其他内容,请在参数名称前面使用关键字 OUT 或 INOUT。

注意

将参数指定为 IN、OUT 或 INOUT 仅对 PROCEDURE 有效。对于 FUNCTION,参数始终被视为 IN 参数。

IN 参数将值传递给过程。过程可能会修改该值,但当过程返回时,调用者看不到修改。 OUT 参数将过程中的值传递回调用者。它在过程中的初始值为 NULL,并且当过程返回时,调用者可以看到它的值。 INOUT 参数由调用者初始化,可以由过程修改,并且过程所做的任何更改在过程返回时对调用者可见。

对于每个 OUT 或 INOUT 参数,请在调用该过程的 CALL 语句中传递一个用户定义的变量,以便您可以在过程返回时获取其值。如果您从另一个存储过程或函数中调用该过程,您还可以将例程参数或局部例程变量作为 OUT 或 INOUT 参数传递。如果您从触发器中调用该过程,您还可以将 NEW.col_name 作为 OUT 或 INOUT 参数传递。

有关未处理条件对过程参数的影响的信息,请参阅 第 15.6.7.8 节“条件处理和 OUT 或 INOUT 参数”。

例程参数不能在例程中准备的语句中引用;请参阅 第 27.8 节“对存储程序的限制”。

以下示例显示了一个简单的存储过程,在给定国家/地区代码的情况下,计算该国家/地区在 world 数据库的 city 表中出现的城市数量。国家/地区代码使用 IN 参数传递,城市计数使用 OUT 参数返回

mysql> delimiter //

mysql> CREATE PROCEDURE citycount (IN country CHAR(3), OUT cities INT)
       BEGIN
         SELECT COUNT(*) INTO cities FROM world.city
         WHERE CountryCode = country;
       END//
Query OK, 0 rows affected (0.01 sec)

mysql> delimiter ;

mysql> CALL citycount('JPN', @cities); -- cities in Japan
Query OK, 1 row affected (0.00 sec)

mysql> SELECT @cities;
+---------+
| @cities |
+---------+
|     248 |
+---------+
1 row in set (0.00 sec)

mysql> CALL citycount('FRA', @cities); -- cities in France
Query OK, 1 row affected (0.00 sec)

mysql> SELECT @cities;
+---------+
| @cities |
+---------+
|      40 |
+---------+
1 row in set (0.00 sec)

该示例使用 mysql 客户端的 delimiter 命令,在定义过程时将语句分隔符从 ; 更改为 //。这使得在过程体中使用的 ; 分隔符可以传递到服务器,而不是由 mysql 本身解释。请参阅 第 27.1 节“定义存储程序”。

RETURNS 子句只能为 FUNCTION 指定,并且是必需的。它指示函数的返回类型,并且函数体必须包含 RETURN value 语句。如果 RETURN 语句返回不同类型的值,则该值将被强制转换为正确的类型。例如,如果函数在 RETURNS 子句中指定了 ENUM 或 SET 值,但 RETURN 语句返回一个整数,则从函数返回的值是对应 ENUM 成员或 SET 成员集的字符串。

以下示例函数接受一个参数,使用 SQL 函数执行操作,并返回结果。在这种情况下,不需要使用 delimiter,因为函数定义不包含内部 ; 语句分隔符

mysql> CREATE FUNCTION hello (s CHAR(20))
mysql> RETURNS CHAR(50) DETERMINISTIC
       RETURN CONCAT('Hello, ',s,'!');
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT hello('world');
+----------------+
| hello('world') |
+----------------+
| Hello, world!  |
+----------------+
1 row in set (0.00 sec)

参数类型和函数返回类型可以声明为使用任何有效的数据类型。如果在 CHARACTER SET 规范之前,则可以使用 COLLATE 属性。

routine_body 由一个有效的 SQL 例程语句组成。这可以是一个简单的语句,例如 SELECT 或 INSERT,也可以是一个使用 BEGIN 和 END 编写的复合语句。复合语句可以包含声明、循环和其他控制结构语句。这些语句的语法在 第 15.6 节“复合语句语法” 中进行了描述。在实践中,存储函数倾向于使用复合语句,除非主体由单个 RETURN 语句组成。

MySQL 允许例程包含 DDL 语句,例如 CREATE 和 DROP。MySQL 还允许存储过程(但不是存储函数)包含 SQL 事务语句,例如 COMMIT。存储函数不得包含执行显式或隐式提交或回滚的语句。SQL 标准并不要求支持这些语句,该标准规定每个 DBMS 供应商都可以决定是否允许它们。

返回结果集的语句可以在存储过程中使用,但不能在存储函数中使用。此禁止包括没有 INTO var_list 子句的 SELECT 语句以及其他语句,例如 SHOW、EXPLAIN 和 CHECK TABLE。对于在函数定义时可以确定为返回结果集的语句,会发生 不允许从函数返回结果集 错误(ER_SP_NO_RETSET)。对于只能在运行时确定为返回结果集的语句,会发生 PROCEDURE %s 在给定上下文中不能返回结果集 错误(ER_SP_BADSELECT)。

不允许在存储例程中使用 USE 语句。当调用例程时,会执行隐式 USE db_name(并在例程终止时撤消)。这会导致例程在执行时具有给定的默认数据库。对例程默认数据库以外的数据库中的对象的引用应使用适当的数据库名称进行限定。

有关存储例程中不允许使用的语句的更多信息,请参阅 第 27.8 节“对存储程序的限制”。

有关从使用具有 MySQL 接口的语言编写的程序中调用存储过程的信息,请参阅 第 15.2.1 节“CALL 语句”。

MySQL 存储在创建或更改例程时生效的 sql_mode 系统变量设置,并始终使用此设置执行例程,而不管例程开始执行时的当前服务器 SQL 模式如何。

从调用者的 SQL 模式切换到例程的 SQL 模式发生在评估参数并将结果值赋给例程参数之后。如果您在严格 SQL 模式下定义了一个例程,但在非严格模式下调用它,则不会在严格模式下将参数赋给例程参数。如果您需要在严格 SQL 模式下分配传递给例程的表达式,则应在严格模式下调用该例程。

COMMENT 特性是 MySQL 的扩展,可用于描述存储例程。此信息由 SHOW CREATE PROCEDURE 和 SHOW CREATE FUNCTION 语句显示。

LANGUAGE 属性指示编写例程所用的语言。服务器会忽略此属性;仅支持 SQL 例程。

如果例程对于相同的输入参数始终产生相同的结果,则该例程被认为是“确定性”的,否则就被认为是“非确定性”的。如果在例程定义中未给出 DETERMINISTIC 或 NOT DETERMINISTIC,则默认为 NOT DETERMINISTIC。要声明函数是确定性的,必须显式指定 DETERMINISTIC。

对例程性质的评估基于创建者的“诚实”:MySQL 不会检查声明为 DETERMINISTIC 的例程是否不包含产生非确定性结果的语句。但是,错误地声明例程可能会影响结果或性能。将非确定性例程声明为 DETERMINISTIC 可能会导致优化器做出错误的执行计划选择,从而导致意外结果。将确定性例程声明为 NONDETERMINISTIC 可能会导致无法使用可用的优化,从而降低性能。

如果启用了二进制日志记录,则 DETERMINISTIC 属性会影响 MySQL 接受哪些例程定义。请参阅第 27.7 节“存储程序二进制日志记录”。

包含 NOW() 函数(或其同义词)或 RAND() 的例程是非确定性的,但它可能仍然是复制安全的。对于 NOW(),二进制日志包含时间戳并正确复制。RAND() 也可以正确复制,只要在例程执行期间只调用一次即可。(您可以将例程执行时间戳和随机数种子视为源和副本上相同的隐式输入。)

一些属性提供了有关例程使用数据的性质的信息。在 MySQL 中,这些属性仅供参考。服务器不会使用它们来约束允许例程执行的语句类型。

  • CONTAINS SQL 指示例程不包含读取或写入数据的语句。如果没有显式给出这些属性中的任何一个,则这是默认设置。此类语句的示例包括 SET @x = 1 或 DO RELEASE_LOCK('abc'),它们会执行但既不读取也不写入数据。

  • NO SQL 指示例程不包含 SQL 语句。

  • READS SQL DATA 指示例程包含读取数据的语句(例如,SELECT),但不包含写入数据的语句。

  • MODIFIES SQL DATA 指示例程包含可能写入数据的语句(例如,INSERT 或 DELETE)。

SQL SECURITY 属性可以是 DEFINER 或 INVOKER,用于指定安全上下文;也就是说,例程是使用例程 DEFINER 子句中命名的帐户的权限执行,还是使用调用它的用户的权限执行。此帐户必须具有访问与例程关联的数据库的权限。默认值为 DEFINER。调用例程的用户必须对其具有 EXECUTE 权限,如果例程在定义者安全上下文中执行,则 DEFINER 帐户也必须具有该权限。

DEFINER 子句指定在例程执行时,对于具有 SQL SECURITY DEFINER 属性的例程,用于检查访问权限的 MySQL 帐户。

如果存在 DEFINER 子句,则 用户 值应为指定为 '用户名'@'主机名' 的 MySQL 帐户、CURRENT_USER 或 CURRENT_USER()。允许的 用户 值取决于您拥有的权限,如第 27.6 节“存储对象访问控制”中所述。另请参阅该部分,以获取有关存储例程安全的更多信息。

如果省略 DEFINER 子句,则默认定义者是执行 CREATE PROCEDURE 或 CREATE FUNCTION 语句的用户。这与显式指定 DEFINER = CURRENT_USER 相同。

在使用 SQL SECURITY DEFINER 属性定义的存储例程的正文中,CURRENT_USER 函数返回例程的 DEFINER 值。有关存储例程中的用户审核的信息,请参阅第 8.2.23 节“基于 SQL 的帐户活动审核”。

请考虑以下过程,该过程显示 mysql.user 系统表中列出的 MySQL 帐户数量的计数

CREATE DEFINER = 'admin'@'localhost' PROCEDURE account_count()
BEGIN
  SELECT 'Number of accounts:', COUNT(*) FROM mysql.user;
END;

该过程被分配了一个 DEFINER 帐户 'admin'@'localhost',无论哪个用户定义它。它使用该帐户的权限执行,无论哪个用户调用它(因为默认安全属性是 DEFINER)。该过程成功或失败取决于调用者是否对其具有 EXECUTE 权限,以及 'admin'@'localhost' 是否对 mysql.user 表具有 SELECT 权限。

现在假设该过程是用 SQL SECURITY INVOKER 属性定义的

CREATE DEFINER = 'admin'@'localhost' PROCEDURE account_count()
SQL SECURITY INVOKER
BEGIN
  SELECT 'Number of accounts:', COUNT(*) FROM mysql.user;
END;

该过程仍然具有 DEFINER 'admin'@'localhost',但在这种情况下,它使用调用用户的权限执行。因此,该过程成功或失败取决于调用者是否对其具有 EXECUTE 权限,以及是否对 mysql.user 表具有 SELECT 权限。

默认情况下,当执行具有 SQL SECURITY DEFINER 属性的例程时,MySQL 服务器不会为 DEFINER 子句中命名的 MySQL 帐户设置任何活动角色,而只设置默认角色。例外情况是,如果启用了 activate_all_roles_on_login 系统变量,在这种情况下,MySQL 服务器会设置授予 DEFINER 用户的所有角色,包括强制角色。因此,默认情况下,当发出 CREATE PROCEDURE 或 CREATE FUNCTION 语句时,不会检查通过角色授予的任何权限。对于存储程序,如果执行时应使用与默认角色不同的角色,则程序体可以执行 SET ROLE 来激活所需的角色。这必须谨慎进行,因为分配给角色的权限可能会更改。

服务器按如下方式处理例程参数、使用 DECLARE 创建的本地例程变量或函数返回值的数据类型

  • 将检查赋值是否存在数据类型不匹配和溢出。转换和溢出问题会导致警告,或在严格 SQL 模式下导致错误。

  • 只能分配标量值。例如,诸如 SET x = (SELECT 1, 2) 之类的语句无效。

  • 对于字符数据类型,如果声明中包含 CHARACTER SET,则使用指定的字符集及其默认排序规则。如果 COLLATE 属性也存在,则使用该排序规则而不是默认排序规则。

    如果 CHARACTER SET 和 COLLATE 不存在,则使用例程创建时生效的数据库字符集和排序规则。为避免服务器使用数据库字符集和排序规则,请为字符数据参数提供显式的 CHARACTER SET 和 COLLATE 属性。

    如果更改数据库默认字符集或排序规则,则必须删除并重新创建要使用新数据库默认值的存储例程。

    数据库字符集和排序规则由 character_set_database 和 collation_database 系统变量的值给出。有关更多信息,请参阅第 12.3.3 节“数据库字符集和排序规则”。