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_nameOUT或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 语句。如果valueRETURN语句返回的值类型不同,值将被强制转换到合适的类型。例如,如果函数在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_listSELECT语句,以及其他语句,如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节,“数据库字符集和排序规则”。