Documentation Home
MySQL 8.4 Reference Manual
Related Documentation Download this Manual
PDF (US Ltr) - 39.8Mb
PDF (A4) - 39.9Mb
Man Pages (TGZ) - 257.9Kb
Man Pages (Zip) - 364.9Kb
Info (Gzip) - 4.0Mb
Info (Zip) - 4.0Mb


MySQL 8.4 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

CREATE FUNCTION 语句也用于 MySQL 支持可加载函数。见第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 权限”

DEFINER 和 SQL SECURITY 子句指定在存储程序执行时使用的安全上下文,后续部分将对其进行描述。

如果存储程序名与内置SQL函数名相同,则在定义或调用时出现语法错误,除非使用空格。因此,避免使用现有SQL函数名为自己的存储程序名。

IGNORE_SPACE SQL 模式只应用于内置函数,不应用于存储程序。无论IGNORE_SPACE 是否启用,总是可以在存储程序名后添加空格。

IF NOT EXISTS 防止出现同名存储程序错误。这选项支持 CREATE FUNCTIONCREATE PROCEDURE 两种语句。

如果已经存在同名的内置函数,使用 CREATE FUNCTION ... IF NOT EXISTS 创建存储函数将成功,但会出现警告,表示与native 函数同名;这与不指定 IF NOT EXISTS 的情况相同。

如果已经存在同名的可加载函数,使用 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节,“Condition Handling and OUT or INOUT Parameters”

routine参数不能在存储程序中引用语句;见第27.8节,“Restrictions on Stored Programs”

以下示例展示了一个简单的存储过程,该过程根据国家代码统计该国城市数量,该国家代码使用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子句,它是必需的。它指示函数的返回类型,函数体必须包含一个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。对于可以在函数定义时间确定返回结果集的语句,会出现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 语句在存储程序中不允许。routine被调用时,隐式执行 USE db_name (当 routine 终止时将其撤销)。这样使得 routine 在执行时拥有给定的默认数据库。对其他数据库中的对象引用应该加以相应的数据库名称。

关于存储程序中不允许的语句,见第27.8节,“存储程序限制”

关于在使用 MySQL 接口编写语言的程序中调用存储过程,见第15.2.1节,“CALL 语句”

MySQL 在创建或修改 routine 时将 sql_mode 系统变量的设置存储,并总是以这个设置强制执行 routine,无论 routine 执行时的当前服务器 SQL 模式是什么

从 invoker 的 SQL 模式切换到 routine 的模模式发生在参数评估和参数分配后。如果你在严格 SQL 模式下定义了 routine 但是在非严格模式下调用,它们的参数分配将不在严格模式下进行。如果你需要在存储程序中将传递给 routine 的表达式分配到严格 SQL 模式,你应该在调用时使得 SQL 模式为严格。

COMMENT特性是 MySQL 扩展,可以用来描述存储程序。这一信息由SHOW CREATE PROCEDURESHOW 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 = 1DO RELEASE_LOCK('abc'),这些语句执行但不读取也不写入数据。

  • NO SQL 表示存储程序中没有 SQL 语句。

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

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

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

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

如果存在DEFINER子句,user值应该是一个 MySQL 账户,指定为 'user_name'@'host_name'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;

该过程无论谁定义它,总是被分配一个 'admin'@'localhost'DEFINER 账户,无论谁调用它,它都执行该账户的权限(因为默认安全特性是 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;

该过程仍然有一个 'admin'@'localhost'DEFINER,但是在这个情况下,它执行的是调用者的权限。因此,过程的结果取决于调用者是否拥有对该过程的 EXECUTE 权限,以及对 mysql.user 表拥有 SELECT 权限。

默认情况下,具有SQL SECURITY 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,则在存储程序创建时的数据库字符集和排序规则将被使用。为了避免服务器使用数据库字符集和排序规则,请为字符数据参数提供明确的CHARACTER SETCOLLATE属性。

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

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