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 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
创建存储函数将成功,但会出现警告,表示与native 函数同名;这与不指定 IF NOT EXISTS
的情况相同。
如果已经存在同名的可加载函数,使用 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节,“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
子句中指定了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
。对于可以在函数定义时间确定返回结果集的语句,会出现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
(当 routine 终止时将其撤销)。这样使得 routine 在执行时拥有给定的默认数据库。对其他数据库中的对象引用应该加以相应的数据库名称。db_name
关于存储程序中不允许的语句,见第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 PROCEDURE
和SHOW 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 = 1
或DO RELEASE_LOCK('abc')
,这些语句执行但不读取也不写入数据。 -
NO SQL
表示存储程序中没有 SQL 语句。 -
READS SQL DATA
表示该routine包含读取数据的语句(例如SELECT
),但不包含写入数据的语句。
SQL SECURITY
特性可以是 DEFINER
或 INVOKER
,以指定安全上下文,即该routine是否使用在 routine DEFINER
子句中命名的账户的权限或调用它的用户的权限。该账户必须拥有与该routine关联的数据库的访问权限。默认值是 DEFINER
。调用该routine的用户必须拥有EXECUTE
特权,如果该routine在定義者安全上下文中执行,則 DEFINER
账户也必须拥有该特权。
DEFINER
子句指定在 routine 执行时用于检查访问权限的 MySQL 账户,该routine具有 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;
该过程无论谁定义它,总是被分配一个 '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 PROCEDURE
或CREATE FUNCTION
语句时,授予的权限不被默认检查。对于存储程序,如果执行应该使用与默认不同的角色,可以在程序体内执行SET ROLE
来激活所需的角色。这需要小心,因为角色分配的权限可以被更改。
服务器对存储程序参数、局部变量或函数返回值的数据类型处理如下:
-
赋值检查数据类型不匹配和溢出。转换和溢出问题在严格 SQL 模式下会产生警告,或者错误。
-
只能将标量值赋值。例如,语句
SET x = (SELECT 1, 2)
无效。 -
对于字符数据类型,如果在声明中包含
CHARACTER SET
,则使用指定的字符集和默认排序规则。如果同时存在COLLATE
属性,则使用该排序规则,而不是默认排序规则。如果不包含
CHARACTER SET
和COLLATE
,则在存储程序创建时的数据库字符集和排序规则将被使用。为了避免服务器使用数据库字符集和排序规则,请为字符数据参数提供明确的CHARACTER SET
和COLLATE
属性。如果您更改了数据库默认字符集或排序规则,那些要使用新默认值的存储程序必须被删除并重新创建。
数据库字符集和排序规则由
character_set_database
和collation_database
系统变量给出。更多信息,请参见第12.3.3节,“数据库字符集和排序规则”。