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 PROCEDURE
和 CREATE FUNCTION
需要 CREATE ROUTINE
权限。如果存在 DEFINER
子句,则所需的权限取决于 user
值,如 第 27.7 节“存储对象访问控制” 中所述。如果启用了二进制日志记录,CREATE FUNCTION
可能需要 SUPER
权限,如 第 27.8 节“存储程序二进制日志记录” 中所述。
默认情况下,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
。
仅对 PROCEDURE
而言,将参数指定为 IN
、OUT
或 INOUT
才有效。对于 FUNCTION
,参数始终被视为 IN
参数。
IN
参数将值传递给过程。过程可能会修改该值,但当过程返回时,调用者看不到该修改。 OUT
参数将值从过程传递回调用者。其初始值在过程中为 NULL
,并且当过程返回时,调用者可以看到其值。 INOUT
参数由调用者初始化,可以由过程修改,并且当过程返回时,调用者可以看到过程所做的任何更改。
对于每个 OUT
或 INOUT
参数,请在调用该过程的 CALL
语句中传递用户定义的变量,以便您可以在过程返回时获取其值。如果您从另一个存储过程或函数中调用该过程,则还可以将例程参数或局部例程变量作为 OUT
或 INOUT
参数传递。如果您从触发器中调用该过程,则还可以将 NEW.
作为 col_name
OUT
或 INOUT
参数传递。
有关未处理条件对过程参数的影响的信息,请参阅 第 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
子句中指定了 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
)。对于只能在运行时确定返回结果集的语句,会发生 过程 %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 PROCEDURE
和 SHOW CREATE FUNCTION
语句显示。
特性 LANGUAGE
指示编写例程所使用的语言。服务器会忽略此特性;仅支持 SQL 例程。如果未提供此特性,则假定语言为 SQL。使用 JavaScript 编写的存储例程(请参阅第 27.3 节“JavaScript 存储程序”)要求使用 LANGUAGE JAVASCRIPT
指定此特性。
如果例程对于相同的输入参数始终生成相同的结果,则该例程被视为“确定性”例程,否则被视为“非确定性”例程。如果在例程定义中未给出 DETERMINISTIC
或 NOT DETERMINISTIC
,则默认为 NOT DETERMINISTIC
。要声明函数是确定性的,必须显式指定 DETERMINISTIC
。
对例程性质的评估基于创建者的“诚实”:MySQL 不会检查声明为 DETERMINISTIC
的例程是否不包含产生非确定性结果的语句。但是,错误声明例程可能会影响结果或性能。将非确定性例程声明为 DETERMINISTIC
可能会导致优化器做出错误的执行计划选择,从而导致意外结果。将确定性例程声明为 NONDETERMINISTIC
可能会导致无法使用可用的优化,从而降低性能。
如果启用了二进制日志记录,则 DETERMINISTIC
特性会影响 MySQL 接受哪些例程定义。请参阅第 27.8 节“存储程序二进制日志记录”。
包含 NOW()
函数(或其同义词)或 RAND()
的例程是非确定性的,但它可能仍然是复制安全的。对于 NOW()
,二进制日志包含时间戳并正确复制。RAND()
也可以正确复制,只要在例程执行期间只调用一次即可。(您可以将例程执行时间戳和随机数种子视为源和副本上相同的隐式输入。)
一些特性提供了有关例程使用数据的性质的信息。在 MySQL 中,这些特性仅供参考。服务器不会使用它们来约束允许例程执行的语句类型。
SQL SECURITY
特性可以是 DEFINER
或 INVOKER
,用于指定安全上下文;也就是说,例程是使用例程 DEFINER
子句中指定的帐户的权限执行,还是使用调用它的用户的权限执行。此帐户必须具有访问与例程关联的数据库的权限。默认值为 DEFINER
。调用例程的用户必须对此具有 EXECUTE
权限,如果例程在定义者安全上下文中执行,则 DEFINER
帐户也必须具有此权限。
DEFINER
子句指定在例程执行时检查具有 SQL SECURITY DEFINER
特性的例程的访问权限时要使用的 MySQL 帐户。
如果存在 DEFINER
子句,则 user
值应为指定为 '
、user_name
'@'host_name
'CURRENT_USER
或 CURRENT_USER()
的 MySQL 帐户。允许的 user
值取决于您拥有的权限,如第 27.7 节“存储对象访问控制”中所述。另请参阅该节,以获取有关存储例程安全的更多信息。
如果省略 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 节“数据库字符集和排序规则”。