### 13.1.17 CREATE PROCEDURE 和 CREATE FUNCTION 语句

[](<>)[](<>)

```
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:
    Valid SQL routine statement
```

这些语句用于创建存储例程（存储过程或函数）。即，指定的例程为服务器所知。默认情况下，存储例程与默认数据库相关联。要将例程与给定数据库显式关联，请将名称指定为*`db_name.sp_name`*当你创建它时。

这`创建函数`在 MySQL 中也使用语句来支持可加载函数。看[第 13.7.4.1 节，“可加载函数的 CREATE FUNCTION 语句”](create-function-loadable.html).可加载函数可以看作是外部存储函数。存储函数与可加载函数共享它们的命名空间。看[第 9.2.5 节，“函数名称解析和解析”](function-resolution.html)，用于描述服务器如何解释对不同类型函数的引用的规则。

要调用存储过程，请使用[`称呼`](call.html)声明（见[第 13.2.1 节，“CALL 语句”](call.html)）。要调用存储函数，请在表达式中引用它。该函数在表达式求值期间返回一个值。

[`创建过程`](create-procedure.html)和[`创建函数`](create-function.html)要求[`创建例程`](privileges-provided.html#priv_create-routine)特权。如果`定义者`条款存在，所需的特权取决于*`用户`*值，如在[第 25.6 节，“存储对象访问控制”](stored-objects-security.html).如果启用了二进制日志记录，[`创建函数`](create-function.html)可能需要[`极好的`](privileges-provided.html#priv_super)特权，如在[第 25.7 节，“存储程序二进制日志”](stored-programs-logging.html).

默认情况下，MySQL 自动授予[`改变常规`](privileges-provided.html#priv_alter-routine)和[`执行`](privileges-provided.html#priv_execute)例程创建者的特权。可以通过禁用[`automatic_sp_privileges`](server-system-variables.html#sysvar_automatic_sp_privileges)系统变量。看[第 25.2.2 节，“存储的例程和 MySQL 权限”](stored-routines-privileges.html).

这`定义者`和`SQL 安全`子句指定在例行执行时检查访问权限时要使用的安全上下文，如本节后面所述。

如果例程名称与内置 SQL 函数的名称相同，则会出现语法错误，除非您在定义例程或稍后调用它时在名称和后面的括号之间使用空格。因此，请避免将现有 SQL 函数的名称用于您自己的存储例程。

这[`忽略空格`](sql-mode.html#sqlmode_ignore_space)SQL 模式适用于内置函数，而不适用于存储例程。在存储的例程名称之后总是允许有空格，无论是否[`忽略空格`](sql-mode.html#sqlmode_ignore_space)已启用。

`如果不存在`如果已经存在同名例程，则防止发生错误。两者都支持此选项`创建函数`和`创建过程`从 MySQL 8.0.29 开始。

如果已存在同名的内置函数，则尝试使用以下方法创建存储函数`创建函数...如果不存在`成功并显示警告，表明它与本机函数具有相同的名称；这与执行相同操作时没有什么不同`创建函数`没有指定的声明`如果不存在`.

如果已存在同名的可加载函数，则尝试使用创建存储函数`如果不存在`成功并发出警告。这和不指定是一样的`如果不存在`.

看[函数名称解析](function-resolution.html#function-name-resolution)， 了解更多信息。

括号内的参数列表必须始终存在。如果没有参数，则为空的参数列表`()`应该使用。参数名称不区分大小写。

每个参数都是一个`在`默认参数。要为参数指定其他内容，请使用关键字`出去`要么`进出`在参数名称之前。

笔记

将参数指定为`在`,`出去`， 要么`进出`仅对一个有效`程序`.为一个`功能`, 参数总是被视为`在`参数。

一个`在`参数将值传递给过程。该过程可能会修改该值，但是当该过程返回时，调用者看不到该修改。一个`出去`参数将一个值从过程传回给调用者。它的初始值为`空值`在过程中，它的值在过程返回时对调用者可见。一个`进出`参数由调用者初始化，可由过程修改，过程返回时调用者可以看到过程所做的任何更改。

对于每个`出去`要么`进出`参数，传递一个用户定义的变量[`称呼`](call.html)调用过程的语句，以便在过程返回时获取其值。如果您从另一个存储过程或函数中调用该过程，您还可以将例程参数或局部例程变量作为`出去`要么`进出`范围。如果您从触发器中调用该过程，您也可以通过`新的。*`col_name`*`作为一个`出去`要么`进出`范围。

有关未处理条件对过程参数的影响的信息，请参阅[第 13.6.7.8 节，“条件处理和 OUT 或 INOUT 参数”](conditions-and-parameters.html).

例程中准备的语句中不能引用例程参数；看[第 25.8 节，“对存储程序的限制”](stored-program-restrictions.html).

下面的例子展示了一个简单的存储过程，给定一个国家代码，计算该国家出现在`城市`的表`世界`数据库。国家代码使用`在`参数，并使用返回的城市计数`出去`范围：

```
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**](mysql.html)客户`分隔符`更改语句分隔符的命令`;`到`//`在定义过程时。这使`;`过程主体中使用的分隔符被传递到服务器而不是被解释[**mysql**](mysql.html)本身。看[第 25.1 节，“定义存储程序”](stored-programs-defining.html).

这`回报`子句只能指定为`功能`，这是强制性的。表示函数的返回类型，函数体必须包含一个`返回 *`价值`*`陈述。如果[`返回`](return.html)语句返回一个不同类型的值，该值被强制转换为正确的类型。例如，如果一个函数指定一个[`枚举`](enum.html)要么[`放`](set.html)中的价值`回报`条款，但[`返回`](return.html)语句返回一个整数，函数返回的值是对应的字符串[`枚举`](enum.html)集合的成员[`放`](set.html)成员。

以下示例函数接受一个参数，使用 SQL 函数执行操作并返回结果。在这种情况下，没有必要使用`分隔符`因为函数定义不包含内部`;`语句分隔符：

```
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)
```

参数类型和函数返回类型可以声明为使用任何有效的数据类型。这`整理`如果前面有a，则可以使用属性`字符集`规格。

这*`例程体`*由有效的 SQL 例程语句组成。这可以是一个简单的语句，例如[`选择`](select.html)要么[`插入`](insert.html), 或使用编写的复合语句`开始`和`结尾`.复合语句可以包含声明、循环和其他控制结构语句。这些语句的语法在[第 13.6 节，“复合语句语法”](sql-compound-statements.html).在实践中，存储函数倾向于使用复合语句，除非主体由单个[`返回`](return.html)陈述。

MySQL 允许例程包含 DDL 语句，例如`创建`和`降低`.MySQL 还允许存储过程（但不包括存储函数）包含 SQL 事务语句，例如[`犯罪`](commit.html).存储函数可能不包含执行显式或隐式提交或回滚的语句。SQL 标准不要求支持这些语句，该标准规定每个 DBMS 供应商可以决定是否允许它们。

返回结果集的语句可以在存储过程中使用，但不能在存储函数中使用。该禁令包括[`选择`](select.html)没有`进入 *`var_list`*`子句和其他语句，例如[`显示`](show.html),[`解释`](explain.html)， 和[`检查表`](check-table.html).对于可以在函数定义时确定返回结果集的语句，a`不允许从函数返回结果集`发生错误（[`ER_SP_NO_RETSET`](https://dev.mysql.com/doc/mysql-errors/8.0/en/server-error-reference.html#error_er_sp_no_retset)）。对于只能在运行时确定返回结果集的语句，a`PROCEDURE %s 无法返回给定上下文中的结果集`发生错误（[`ER_SP_BADSELECT`](https://dev.mysql.com/doc/mysql-errors/8.0/en/server-error-reference.html#error_er_sp_badselect)）。

[`采用`](use.html)不允许存储例程中的语句。当一个例程被调用时，一个隐式的`采用 *`数据库名称`*`执行（并在例程终止时撤消）。导致例程在执行时具有给定的默认数据库。对常规默认数据库以外的数据库中对象的引用应使用适当的数据库名称进行限定。

有关存储例程中不允许使用的语句的其他信息，请参阅[第 25.8 节，“对存储程序的限制”](stored-program-restrictions.html).

有关从以具有 MySQL 接口的语言编写的程序中调用存储过程的信息，请参阅[第 13.2.1 节，“CALL 语句”](call.html).

MySQL 存储[`sql_mode`](server-system-variables.html#sysvar_sql_mode)系统变量设置在创建或更改例程时生效，并始终在此设置生效的情况下执行例程，*当例程开始执行时，无论当前服务器 SQL 模式如何*.

从调用程序的 SQL 模式切换到例程的 SQL 模式发生在对参数进行评估并将结果值分配给例程参数之后。如果您在严格 SQL 模式下定义例程，但在非严格模式下调用它，则在严格模式下不会将实参分配给例程参数。如果您要求传递给例程的表达式以严格的 SQL 模式分配，您应该调用具有有效严格模式的例程。

这`评论`特征是MySQL的扩展，可以用来描述存储例程。该信息由[`显示创建过程`](show-create-procedure.html)和[`显示创建函数`](show-create-function.html)陈述。

这`语言`特征表示编写例程所使用的语言。服务器忽略这个特性；仅支持 SQL 例程。

如果一个例程对于相同的输入参数总是产生相同的结果，则该例程被认为是“确定性的”，否则被认为是“非确定性的”。如果两者都没有`确定性`也不`不确定的`在例程定义中给出，默认为`不确定的`.要声明函数是确定性的，您必须指定`确定性`明确地。

对例程性质的评估基于创建者的“诚实”：MySQL 不检查例程是否声明`确定性`没有产生不确定结果的陈述。但是，错误地声明例程可能会影响结果或影响性能。将非确定性例程声明为`确定性`可能会导致优化器做出不正确的执行计划选择，从而导致意外结果。将确定性例程声明为`不确定的`可能会通过导致不使用可用的优化来降低性能。

如果启用二进制日志记录，则`确定性`特性影响 MySQL 接受哪些例程定义。看[第 25.7 节，“存储程序二进制日志”](stored-programs-logging.html).

一个包含[`现在（）`](date-and-time-functions.html#function_now)功能（或其同义词）或[`兰德（）`](mathematical-functions.html#function_rand)是不确定的，但它可能仍然是复制安全的。为了[`现在（）`](date-and-time-functions.html#function_now)，二进制日志包含时间戳并正确复制。[`兰德（）`](mathematical-functions.html#function_rand)只要在例程执行期间仅调用一次，它也会正确复制。（您可以将例程执行时间戳和随机数种子视为在源和副本上相同的隐式输入。）

几个特征提供了有关例程使用的数据的性质的信息。在 MySQL 中，这些特性只是建议性的。服务器不使用它们来限制例程允许执行的语句类型。

-   `包含 SQL`表示例程不包含读取或写入数据的语句。如果没有明确给出这些特征，则这是默认设置。此类陈述的示例是`设置@x = 1`要么`做 RELEASE_LOCK('abc')`，它执行但既不读取也不写入数据。

-   `没有 SQL`表示例程不包含 SQL 语句。

-   `读取 SQL 数据`表示例程包含读取数据的语句（例如，[`选择`](select.html))，但不是写入数据的语句。

-   `修改 SQL 数据`表示例程包含可能写入数据的语句（例如，[`插入`](insert.html)要么[`删除`](delete.html)）。

    这`SQL 安全`特征可以是`定义者`要么`调用者`指定安全上下文；即例程是否使用例程中命名的帐户的权限执行`定义者`子句或调用它的用户。此帐户必须有权访问与例程关联的数据库。默认值为`定义者`.调用例程的用户必须拥有[`执行`](privileges-provided.html#priv_execute)特权，因为必须`定义者`如果例程在定义者安全上下文中执行，则说明。

    这`定义者`子句指定在例程执行时检查具有以下权限的例程的访问权限时要使用的 MySQL 帐户`SQL 安全定义器`特征。

    如果`定义者`条款存在，则*`用户`*值应该是指定为的 MySQL 帐户`'*`用户名`*'@'*`主机名`*'`,[`当前用户`](information-functions.html#function_current-user)， 要么[`当前用户（）`](information-functions.html#function_current-user).允许的*`用户`*值取决于您拥有的特权，如在[第 25.6 节，“存储对象访问控制”](stored-objects-security.html).另请参阅该部分以获取有关存储的例程安全性的其他信息。

    如果`定义者`子句被省略，默认定义者是执行[`创建过程`](create-procedure.html)要么[`创建函数`](create-function.html)陈述。这与指定相同`定义者 = CURRENT_USER`明确地。

    在使用定义的存储例程的主体内`SQL 安全定义器`特点[`当前用户`](information-functions.html#function_current-user)函数返回例程的`定义者`价值。有关存储例程中的用户审核的信息，请参阅[第 6.2.23 节，“基于 SQL 的帐户活动审计”](account-activity-auditing.html).

    考虑以下过程，它显示了列表中列出的 MySQL 帐户数量的计数。`mysql.user`系统表：


```
CREATE DEFINER = 'admin'@'localhost' PROCEDURE account_count()
BEGIN
  SELECT 'Number of accounts:', COUNT(*) FROM mysql.user;
END;
```

该过程被分配了一个`定义者`帐户`'管理员'@'本地主机'`无论哪个用户定义它。无论哪个用户调用它，它都会以该帐户的权限执行（因为默认的安全特性是`定义者`）。该过程成功或失败取决于调用者是否具有[`执行`](privileges-provided.html#priv_execute)它的特权和`'管理员'@'本地主机'`有[`选择`](privileges-provided.html#priv_select)的特权`mysql.user`桌子。

现在假设过程定义为`SQL 安全调用程序`特征：

```
CREATE DEFINER = 'admin'@'localhost' PROCEDURE account_count()
SQL SECURITY INVOKER
BEGIN
  SELECT 'Number of accounts:', COUNT(*) FROM mysql.user;
END;
```

该程序仍有`定义者`的`'管理员'@'本地主机'`，但在这种情况下，它以调用用户的权限执行。因此，过程成功或失败取决于调用者是否拥有[`执行`](privileges-provided.html#priv_execute)它的特权和[`选择`](privileges-provided.html#priv_select)的特权`mysql.user`桌子。

默认情况下，当一个例程与`SQL 安全定义器`执行特性时，MySQL Server 不会为在`定义者`子句，只有默认角色。例外情况是，如果[`activate_all_roles_on_login`](server-system-variables.html#sysvar_activate_all_roles_on_login)系统变量已启用，在这种情况下 MySQL 服务器设置授予的所有角色`定义者`用户，包括强制角色。因此，默认情况下，不会检查通过角色授予的任何特权。[`创建过程`](create-procedure.html)要么[`创建函数`](create-function.html)发表声明。对于存储的程序，如果应该以不同于默认角色的角色执行，则程序主体可以执行[`设定角色`](set-role.html)激活所需的角色。由于可以更改分配给角色的权限，因此必须谨慎执行此操作。

服务器处理例程参数的数据类型，本地例程变量创建于[`宣布`](declare.html), 或函数返回值如下：

-   检查分配是否存在数据类型不匹配和溢出。转换和溢出问题会导致警告或严格 SQL 模式下的错误。

-   只能分配标量值。例如，这样的语句`设置 x = (选择 1, 2)`是无效的。

-   对于字符数据类型，如果`字符集`如果包含在声明中，则使用指定的字符集及其默认排序规则。如果`整理`属性也存在，使用该排序规则而不是默认排序规则。

    如果`字符集`和`整理`不存在，则使用在例程创建时有效的数据库字符集和排序规则。为避免让服务器使用数据库字符集和排序规则，请提供显式`字符集`和一个`整理`字符数据参数的属性。

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

    数据库字符集和排序规则由[`字符集数据库`](server-system-variables.html#sysvar_character_set_database)和[`collat​​ion_database`](server-system-variables.html#sysvar_collation_database)系统变量。有关详细信息，请参阅[第 10.3.3 节，“数据库字符集和排序规则”](charset-database.html).
