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


MySQL 9.0 参考手册  /  ...  /  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 | JAVASCRIPT }
  | [NOT] DETERMINISTIC
  | { CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA }
  | SQL SECURITY { DEFINER | INVOKER }
}

routine_body:
    SQL routine or JavaScript statements

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

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

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

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

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

DEFINERSQL SECURITY 子句指定在例程执行时检查访问权限的安全上下文,如本节后面所述。

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

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

IF NOT EXISTS 可防止在已存在同名例程时发生错误。 CREATE FUNCTIONCREATE PROCEDURE 均支持此选项。

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

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

有关更多信息,请参阅 函数名称解析

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

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

注意

仅对 PROCEDURE 而言,将参数指定为 INOUTINOUT 才有效。对于 FUNCTION,参数始终被视为 IN 参数。

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

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

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

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

以下示例显示了一个简单的存储过程,在给定国家/地区代码的情况下,该过程会计算该国家/地区在 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 子句中指定了 ENUMSET 值,但 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 例程语句组成。这可以是简单语句(如 SELECTINSERT),也可以是使用 BEGINEND 编写的复合语句。复合语句可以包含声明、循环和其他控制结构语句。这些语句的语法在 第 15.6 节“复合语句语法” 中进行了描述。实际上,存储函数倾向于使用复合语句,除非函数体由单个 RETURN 语句组成。

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

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

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

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

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

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

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

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

特性 LANGUAGE 指示编写例程所使用的语言。服务器会忽略此特性;仅支持 SQL 例程。如果未提供此特性,则假定语言为 SQL。使用 JavaScript 编写的存储例程(请参阅第 27.3 节“JavaScript 存储程序”)要求使用 LANGUAGE JAVASCRIPT 指定此特性。

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

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

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

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

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

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

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

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

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

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

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

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

如果省略 DEFINER 子句,则默认定义者是执行 CREATE PROCEDURECREATE 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 PROCEDURECREATE FUNCTION 语句时,默认情况下不会检查通过角色授予的任何权限。对于存储程序,如果执行应该使用与默认角色不同的角色进行,则程序体可以执行 SET ROLE 来激活所需的角色。这必须谨慎操作,因为分配给角色的权限可能会更改。

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

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

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

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

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

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

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