MySQL入門系列:儲存程式(三)之儲存過程簡介

小孩子4919發表於2019-03-21

儲存過程

建立儲存過程

儲存函式儲存過程都屬於儲存例程,都是對某些語句的一個封裝。儲存函式側重於執行這些語句並返回一個值,而儲存過程更側重於單純的去執行這些語句。先看一下儲存過程的定義語句:

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進階的難度,讓學習曲線更平滑一點~

MySQL入門系列:儲存程式(三)之儲存過程簡介

相關文章