MySQL入门系列:存储程序(三)之存储过程简介

存储过程

创建存储过程

存储函数存储过程都属于存储例程,都是对某些语句的一个封装。存储函数侧重于执行这些语句并返回一个值,而存储过程更侧重于单纯的去执行这些语句。先看一下存储过程的定义语句:

CREATE PROCEDURE 存储过程名称([参数列表])
BEGIN
    需要执行的语句
END    
复制代码

存储函数最直观的不同点就是,存储过程的定义不需要声明返回值类型。为了更直观的理解,我们先定义一个存储过程看看:

mysql> delimiter $
mysql> CREATE PROCEDURE t1_operation(
    ->     m1_value INT,
    ->     n1_value CHAR(1)
    -> )
    -> BEGIN
    ->     SELECT * FROM t1;
    ->     INSERT INTO t1(m1, n1) VALUES(m1_value, n1_value);
    ->     SELECT * FROM t1;
    -> END $
Query OK, 0 rows affected (0.00 sec)

mysql> delimiter ;
mysql>
复制代码

我们建立了一个名叫t1_operation的存储过程,它接收两个参数,一个是INT类型的,一个是CHAR(1)类型的。这个存储过程做了3件事儿,一件是查询一下t1表中的数据,第二件是根据接收的参数来向t1表中插入一条语句,第三件是再次查询一下t1表中的数据。

存储过程的调用

存储函数执行语句并返回一个值,所以常用在表达式中。存储过程偏向于调用那些语句,并不能用在表达式中,我们需要显式的使用CALL语句来调用一个存储过程

CALL 存储过程([参数列表]);
复制代码

比方说我们调用一下t1_operation存储过程可以这么写:

mysql> CALL t1_operation(4, 'd');
+------+------+
| m1   | n1   |
+------+------+
|    1 | a    |
|    2 | b    |
|    3 | c    |
+------+------+
3 rows in set (0.00 sec)

+------+------+
| m1   | n1   |
+------+------+
|    1 | a    |
|    2 | b    |
|    3 | c    |
|    4 | d    |
+------+------+
4 rows in set (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

mysql>
复制代码

从执行结果中可以看到,存储过程在执行中产生的所有结果集,全部将会被显示到客户端

小贴士:

只有查询语句才会产生结果集,更新语句是不产生结果集的,所以那条`INSERT`语句所产生的输出没有被显示出来。
复制代码

查看和删除存储过程

存储函数类似,存储过程也有相似的查看和删除语句,我们下边只列举一下相关语句,就不举例子了。

查看当前数据库中创建的存储过程都有哪些的语句:

SHOW PROCEDURE STATUS [LIKE 需要匹配的函数名]
复制代码

查看某个存储过程定义的语句:

SHOW CREATE PROCEDURE 存储过程名称
复制代码

删除存储过程的语句:

DROP PROCEDURE 存储过程名称
复制代码

存储过程中的语句

上边在唠叨存储函数中使用到的各种语句,包括变量的使用、判断、循环结构、注释的使用都可以被用在存储过程中,这里就不再赘述了。

存储过程的参数类型

存储函数牛逼的一点是,存储过程在定义参数的时候可以选择参数类型(注意!不是数据类型),就像是这个样子:

参数类型 参数名 数据类型
复制代码

这个所谓的参数类型有下边3种:

参数类型 实际参数是否必须是变量 描述
IN 用于调用者向过程传递数据,如果该参数在过程中被修改,调用者不可见
OUT 用于把过程产生的结果放到此类型的参数中,过程结束后调用者可以通过访问该参数来获取过程执行的结果
INOUT 综合INOUT的特点,既可以用于调用者向过程传递数据,也可以用于存放过程中产生的结果以供调用者使用

这么直接描述有些生硬哈,我们一个一个来举例子仔细分析一下:

  • IN参数类型

    先定义一个类型参数是IN的存储过程p_in

    mysql> delimiter $
    复制代码

mysql> CREATE PROCEDURE p_in ( -> IN arg INT -> ) -> BEGIN -> -- 语句一:读取参数 -> SELECT arg; -> -- 语句二:为参数赋值 -> SET arg = 123; -> END $ Query OK, 0 rows affected (0.00 sec)

mysql> delimiter ;
mysql>
```
这个`p_in`存储过程只有一个参数`arg`,它的参数类型是`IN`,这个存储过程实际执行两个语句,第一个语句是用来读取参数的值,第二个语句是给参数赋值。我们调用一下`p_in`:
```
mysql> SET @a = 1;
Query OK, 0 rows affected (0.00 sec)

mysql> CALL p_in(@a);
+------+
| arg  |
+------+
|    1 |
+------+
1 row in set (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

mysql> SELECT @a;
+------+
| @a   |
+------+
|    1 |
+------+
1 row in set (0.00 sec)

mysql>
```
我们在客户端定义了一个变量`a`并赋值`1`,因为它是在客户端定义的,所以需要加`@`前缀,然后把它当作参数传给`p_in`存储过程。从结果中可以看出,第一个读取语句被成功执行,虽然第二个语句没有报错,但是再存储过程执行完毕后,再次查看变量`a`的值并没有改变,这也就是说:<span style="color:red">IN参数类型的变量只能用于读取,对类型的变量赋值是不会被调用者看到的</span>。

另外,<span style="color:red">因为对于参数类型是`IN`的参数,我们只是想在存储函数执行中使用它,并不需要把执行结果存储到它里边,所以除了让变量作为函数参数,常量也是可以的</span>,比如这样:
```
mysql> CALL p_in(1);
+------+
| arg  |
+------+
|    1 |
+------+
1 row in set (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

mysql>
```
复制代码
  • OUT参数类型

    先定义一个类型参数是OUT的存储过程p_out

    mysql> delimiter $
    复制代码

mysql> CREATE PROCEDURE p_out ( -> OUT a INT -> ) -> BEGIN -> SELECT a; -> SET a = 123; -> END $ Query OK, 0 rows affected (0.00 sec)

mysql> delimiter ;
mysql>
```
这个`p_out`存储过程只有一个参数`arg`,它的参数类型是`OUT`,`p_out`存储过程也有两个语句,一个用于读取参数的值,另一个用于为参数赋值,我们调用一下`p_out`:
```
mysql> SET @b = 2;
Query OK, 0 rows affected (0.00 sec)

mysql> CALL p_out(@b);
+------+
| a    |
+------+
| NULL |
+------+
1 row in set (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

mysql> SELECT @b;
+------+
| @b   |
+------+
|  123 |
+------+
1 row in set (0.00 sec)

mysql>
```
我们在客户端定义了一个变量`b`并赋值`2`,然后把它当作参数传给`p_out`存储过程。从结果中可以看出,第一个读取语句并没有获取到参数的值,在存储过程执行完毕之后,再次读取变量`b`的值,发现它的值已经被设置成`123`,说明在过程中对该变量的赋值对调用者是可见的!这也就是说:<span style="color:red">OUT参数类型的变量只能用于赋值,对类型的变量赋值是会被调用者看到的</span>。

另外,<span style="color:red">由于`OUT`参数类型的参数只是为了用于在过程中赋值后被调用者查看,那实际的参数就不允许是常量,常量还怎么赋值啊</span>!
复制代码
  • INOUT参数类型

    知道了INOUT参数类型的意思,INOUT也就明白了,这种类型的参数既可以在存储过程中被读取,也可以被赋值后被调用者看到,所以要求实际的参数必须是一个变量,不然还怎么赋值啊!INOUT参数类型就不具体举例子了,你自己试试哈~

需要注意的是,如果我们不写明参数类型的话,该参数的类型默认是IN,我们之前一直没有注明参数类型,所以之前使用的参数类型都是IN

由于可以传入多个OUT或者INOUT类型的参数,所以我们可以在一个存储过程中获得多个结果,比如这样:

mysql> delimiter $
mysql> CREATE PROCEDURE get_score_data(
    ->     OUT max_score DOUBLE,
    ->     OUT min_score DOUBLE,
    ->     OUT avg_score DOUBLE,
    ->     s VARCHAR(100)
    -> )
    -> BEGIN
    ->     SELECT MAX(score), MIN(score), AVG(score) FROM student_score WHERE subject = s INTO max_score, min_score, avg_score;
    -> END $
Query OK, 0 rows affected (0.02 sec)

mysql> delimiter ;
mysql>
复制代码

我们定义的这个get_score_data存储过程接受4个参数,前三个参数都是OUT类型的参数,第四个参数没写参数类型,默认就是IN类型。存储过程的内容是将指定学科的最高分、最低分、平均分赋值给三个OUT类型的参数。在这个存储过程执行完之后,我们可以根据通过访问这几个OUT类型的参数来获得相应的最高分、最低分以及平均分:

mysql> CALL get_score_data(@a, @b, @c, '母猪的产后护理');
Query OK, 1 row affected (0.01 sec)

mysql> SELECT @a, @b, @c;
+------+------+------+
| @a   | @b   | @c   |
+------+------+------+
|  100 |   55 |   73 |
+------+------+------+
1 row in set (0.00 sec)

mysql>
复制代码

这个例子说明了:我们可以在存储过程中向调用者返回多个值,而存储函数只能返回一个值

存储过程和存储函数的不同点

存储过程存储函数都是某些语句的一个封装,而且使用的语法格式都是一样的,下边我们着重说一下它们的不同点以加深大家的对这两者区别的印象:

  1. 存储函数在定义时需要显式用RETURNS语句标明返回的数据类型,而且在函数体中必须使用RETURN语句来显式指定返回的值,存储过程不需要。

  2. 存储函数的参数类型只能是IN,而存储过程支持INOUTINOUT三种参数类型。

  3. 存储函数只能返回一个值,而存储过程可以通过设置多个OUT类型的参数来返回多个结果。

  4. 存储函数执行过程中产生的结果集并不会被显示到客户端,而存储过程执行过程中产生的结果集会被显示到客户端。

  5. 存储函数的调用直接使用在表达式中,而存储过程只能通过CALL语句来显式调用。

小册

本系列专栏都是MySQL入门知识,想看进阶知识可以到小册中查看:《MySQL是怎样运行的:从根儿上理解MySQL》的链接 。小册的内容主要是从小白的角度出发,用比较通俗的语言讲解关于MySQL进阶的一些核心概念,比如记录、索引、页面、表空间、查询优化、事务和锁等,总共的字数大约是三四十万字,配有上百幅原创插图。主要是想降低普通程序员学习MySQL进阶的难度,让学习曲线更平滑一点~

猜你喜欢

转载自juejin.im/post/5c92e6abf265da61103b4525