Documentation Home
MySQL 8.3 Reference Manual
Related Documentation Download this Manual
PDF (US Ltr) - 40.8Mb
PDF (A4) - 40.9Mb
Man Pages (TGZ) - 294.0Kb
Man Pages (Zip) - 409.0Kb
Info (Gzip) - 4.0Mb
Info (Zip) - 4.0Mb
Excerpts from this Manual

MySQL 8.3 Reference Manual  /  ...  /  CREATE PROCEDURE and CREATE FUNCTION Statements

15.1.17 创建过程和创建函数语句

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 PROCEDURECREATE FUNCTION 需要 CREATE ROUTINE 权限。如果存在 DEFINER 子句,则所需权限取决于 user 值,如 第 27.6 节,“存储对象访问控制” 中所讨论的那样。如果启用了二进制日志记录,CREATE FUNCTION 可能需要 SUPER 权限,如 第 27.7 节,“存储程序二进制日志记录” 中所讨论的那样。

默认情况下,MySQL 会自动授予 routine 创建者 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

Note

指定参数为 INOUTINOUT 仅适用于 PROCEDURE。对于 FUNCTION,参数始终被视为 IN 参数。

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

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

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

routine 参数不能在过程中准备的语句中引用;请参阅 第 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 语句。如果 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)

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

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

MySQL 允许存储过程包含 DDL 语句,例如 CREATEDROP。 MySQL 也允许存储过程(但不是存储函数)包含 SQL 事务语句,例如 COMMIT。 存储函数不能包含执行明确或隐式提交或回滚的语句。 对这些语句的支持不是 SQL 标准所要求的,每个 DBMS 厂商可以决定是否允许它们。

可以在存储过程中使用返回结果集的语句,但不能在存储函数中使用。 这包括 SELECT 语句,哪些没有 INTO var_list 子句,以及其他语句,例如 SHOWEXPLAINCHECK TABLE。 对于可以在函数定义时确定返回结果集的语句,将发生 Not allowed to return a result set from a function 错误 (ER_SP_NO_RETSET)。 对于只能在运行时确定返回结果集的语句,将发生 PROCEDURE %s can't return a result set in the given context 错误 (ER_SP_BADSELECT)。

USE 语句在存储过程中不允许。当一个过程被调用时,隐式地执行 USE db_name(并在过程终止时撤销)。这使得过程在执行时具有给定的默认数据库。对其他数据库中对象的引用应该用适当的数据库名称限定。

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

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

MySQL 将 sql_mode 系统变量的设置存储在创建或修改过程时,并总是使用该设置来执行过程,无论当前服务器 SQL 模式何时开始执行 过程

从调用者的 SQL 模式到过程的切换发生在参数评估和将结果值分配给过程参数之后。如果您在严格 SQL 模式下定义过程,但是在非严格模式下调用它,那么参数的分配将不会在严格模式下进行。如果您需要确保传递给过程的表达式在严格 SQL 模式下分配,您应该在严格模式下调用过程。

The COMMENT 特征是 MySQL 的扩展,可以用来描述存储过程。该信息由 SHOW CREATE PROCEDURESHOW CREATE FUNCTION 语句显示。

The LANGUAGE 特征指示过程的编写语言。服务器忽略该特征;仅支持 SQL 过程。

如果过程总是为相同的输入参数产生相同的结果,则该过程被认为是 确定的,否则为 不确定的。如果在过程定义中没有指定 DETERMINISTICNOT DETERMINISTIC,则默认为 NOT DETERMINISTIC。要声明函数为确定的,必须明确指定 DETERMINISTIC

评估routine的性质基于创建者的“诚实”:MySQL不会检查声明为DETERMINISTIC的routine是否包含产生非确定性结果的语句。然而,misdeclaring一个routine可能会影响结果或性能。将非确定性routine声明为DETERMINISTIC可能会导致优化器做出错误的执行计划选择。将确定性routine声明为NONDETERMINISTIC可能会降低性能,因为可用的优化不会被使用。

如果启用了二进制日志记录,则DETERMINISTIC特征会影响MySQL接受的routine定义。见第27.7节,“存储程序二进制日志记录”

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

几个特征提供了关于routine使用数据的信息。在MySQL中,这些特征仅供参考。服务器不使用它们来约束routine可以执行的语句类型。

  • CONTAINS SQL表明routine不包含读取或写入数据的语句。这是默认值,如果没有明确给出这些特征。例如,SET @x = 1DO RELEASE_LOCK('abc')语句执行但不读取或写入数据。

  • NO SQL表明routine不包含SQL语句。

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

  • MODIFIES SQL DATA表明routine包含可能写入数据的语句(例如,INSERTDELETE)。

SQL 安全特性可以是 DEFINERINVOKER,以指定安全上下文,即是否使用账户 DEFINER 子句中指定的账户或调用它的用户的权限执行例程。该账户必须有权限访问与该例程关联的数据库。默认值是 DEFINER。调用该例程的用户必须拥有 EXECUTE 权限,同时 DEFINER 账户也必须拥有该权限,如果该例程在定义者安全上下文中执行。

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

如果存在 DEFINER 子句,则 user 值应该是一个指定为 'user_name'@'host_name' 的 MySQL 账户,或者是 CURRENT_USERCURRENT_USER()。允许的 user 值取决于您拥有的权限,如 第 27.6 节,“存储对象访问控制” 中所讨论的那样。还可以在该节中找到关于存储例程安全的更多信息。

如果省略了 DEFINER 子句,则默认定义者是执行 CREATE PROCEDURECREATE FUNCTION 语句的用户。这相当于显式指定 DEFINER = CURRENT_USER

在具有 SQL SECURITY DEFINER 特性的存储例程的主体中, CURRENT_USER 函数返回例程的 DEFINER 值。关于存储例程中的用户审核的信息,请参阅 第 8.2.23 节,“基于 SQL 的账户活动审核”

考虑以下过程,该过程显示 MySQL 账户在 mysql.user 系统表中的数量:

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

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

现在假设该过程定义了 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 特权,并且具有 SELECT 特权对于 mysql.user 表。

默认情况下,当具有 SQL SECURITY DEFINER 特征的例程被执行时,MySQL 服务器不会为 DEFINER 子句中命名的 MySQL 账户设置任何活动角色,只设置默认角色。唯一的例外是,如果 activate_all_roles_on_login 系统变量启用了,在这种情况下,MySQL 服务器将设置 DEFINER 用户的所有角色,包括强制角色。因此,通过角色授予的特权默认情况下不会被检查。当 CREATE PROCEDURECREATE FUNCTION 语句被发出时。对于存储程序,如果执行应该与默认角色不同,程序体可以执行 SET ROLE 来激活所需的角色。这必须小心,因为角色授予的特权可以被更改。

服务器处理存储过程参数、局部存储变量创建的数据类型或函数返回值,如下所示:

  • 分配检查数据类型不匹配和溢出。转换和溢出问题将导致警告或严格 SQL 模式下的错误。

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

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

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

    如果您更改了数据库的默认字符集或排序规则,存储的routine必须被删除并重新创建,以便使用新的数据库默认值。

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