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 会自动授予 routine 创建者 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
语句中传递用户定义的变量,以便在过程返回时获取其值。如果您从另一个存储过程或函数中调用过程,可以将routine参数或局部routine变量作为 OUT
或 INOUT
参数传递。如果您从触发器中调用过程,可以将 NEW.
作为 col_name
OUT
或 INOUT
参数传递。
有关未处理条件对过程参数的影响,请参阅 第 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
子句中指定了 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)
参数类型和函数返回类型可以声明为任何有效的数据类型。COLLATE
属性可以在 CHARACTER SET
规范之前使用。
该 routine_body
由一个有效的 SQL 存储过程语句组成。 这可以是一个简单的语句,例如 SELECT
或 INSERT
,或者是一个使用 BEGIN
和 END
编写的复合语句。 复合语句可以包含声明、循环和其他控制结构语句。 这些语句的语法在 第 15.6 节,“复合语句语法” 中描述。 在实践中,存储函数往往使用复合语句,除非主体由单个 RETURN
语句组成。
MySQL 允许存储过程包含 DDL 语句,例如 CREATE
和 DROP
。 MySQL 也允许存储过程(但不是存储函数)包含 SQL 事务语句,例如 COMMIT
。 存储函数不能包含执行明确或隐式提交或回滚的语句。 对这些语句的支持不是 SQL 标准所要求的,每个 DBMS 厂商可以决定是否允许它们。
可以在存储过程中使用返回结果集的语句,但不能在存储函数中使用。 这包括 SELECT
语句,哪些没有 INTO
子句,以及其他语句,例如 var_list
SHOW
、EXPLAIN
和 CHECK 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 PROCEDURE
和 SHOW CREATE FUNCTION
语句显示。
The LANGUAGE
特征指示过程的编写语言。服务器忽略该特征;仅支持 SQL 过程。
如果过程总是为相同的输入参数产生相同的结果,则该过程被认为是 “确定的”,否则为 “不确定的”。如果在过程定义中没有指定 DETERMINISTIC
或 NOT 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可以执行的语句类型。
SQL 安全特性可以是 DEFINER
或 INVOKER
,以指定安全上下文,即是否使用账户 DEFINER
子句中指定的账户或调用它的用户的权限执行例程。该账户必须有权限访问与该例程关联的数据库。默认值是 DEFINER
。调用该例程的用户必须拥有 EXECUTE
权限,同时 DEFINER
账户也必须拥有该权限,如果该例程在定义者安全上下文中执行。
DEFINER
子句指定了在执行时检查访问权限的 MySQL 账户,该账户用于具有 SQL SECURITY DEFINER
特性的例程。
如果存在 DEFINER
子句,则 user
值应该是一个指定为 '
的 MySQL 账户,或者是 user_name
'@'host_name
'CURRENT_USER
或 CURRENT_USER()
。允许的 user
值取决于您拥有的权限,如 第 27.6 节,“存储对象访问控制” 中所讨论的那样。还可以在该节中找到关于存储例程安全的更多信息。
如果省略了 DEFINER
子句,则默认定义者是执行 CREATE PROCEDURE
或 CREATE 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 PROCEDURE
或 CREATE FUNCTION
语句被发出时。对于存储程序,如果执行应该与默认角色不同,程序体可以执行 SET ROLE
来激活所需的角色。这必须小心,因为角色授予的特权可以被更改。
服务器处理存储过程参数、局部存储变量创建的数据类型或函数返回值,如下所示:
-
分配检查数据类型不匹配和溢出。转换和溢出问题将导致警告或严格 SQL 模式下的错误。
-
只能分配标量值。例如,语句
SET x = (SELECT 1, 2)
是无效的。 -
对于字符数据类型,如果在声明中包括
CHARACTER SET
,则使用指定的字符集及其默认排序规则。如果还存在COLLATE
属性,则使用该排序规则而不是默认排序规则。如果
CHARACTER SET
和COLLATE
不存在,则使用在routine创建时生效的数据库字符集和排序规则。为了避免服务器使用数据库字符集和排序规则,请为字符数据参数提供明确的CHARACTER SET
和COLLATE
属性。如果您更改了数据库的默认字符集或排序规则,存储的routine必须被删除并重新创建,以便使用新的数据库默认值。
数据库字符集和排序规则由
character_set_database
和collation_database
系统变量的值给出。有关更多信息,请参阅第 12.3.3 节,“数据库字符集和排序规则”。