mysql存儲過程梳理
0.前言-為什么要使用存儲過程
在做一些復雜業務邏輯時,如果程序與數據庫進行多次交互,會增加連接資源的消耗,增加整個業務邏輯處理的時間,存儲過程能夠減少程序與數據庫的交互次數,節省連接資源,加快處理速度。
以下示例均在8.0.21版本下測試成功。
1.優缺點
1.1 優點
-
存儲過程能夠減少程序與數據庫的交互次數,節省連接資源,加快處理速度
-
創建時會先編譯,后續的調用都不需要再次編譯
-
生產環境中,可以通過直接修改存儲過程的方式修改業務邏輯,而不用重啟服務器
1.2 缺點
- 過程化編譯,維護成本高,尤其是在一些復雜的業務邏輯中
- 測試不方便,無法debug
- 不同數據庫之間可移植性差,語法不一樣
2.基本語法
2.1 基本關鍵字
2.1.1 delimiter
聲明結束符為:
delimiter
告訴mysql解釋器,該段命令是否已經結束了,mysql是否可以執行了。
默認情況下,delimiter是分號;。在命令行客戶端中,如果有一行命令以分號結束,
那么回車后,mysql將會執行該命令。如輸入下面的語句
mysql> select * from test_table;
然后回車,那么MySQL將立即執行該語句。
但有時候,不希望MySQL這么做。在為可能輸入較多的語句,且語句中包含有分號
例如下面的意思為,以$$作為結束符號,表示這個存儲過程的結尾
delimiter $$
2.1.2 create
創建一個存儲過程
CREATE PROCEDURE
2.1.3 drop
刪除一個存儲過程
drop procedure pro_name
2.1.4 begin|end
邏輯代碼塊
-- 開始
BEGIN
...
-- 結束
END
2.2 語法結構
2.2.1 示例
DELIMITER $$
CREATE
PROCEDURE demo_procedure()
BEGIN
select 'hello procedure';
END $$
DELIMITER ;
call demo_procedure();
2.2.2 總體結構
[]內的參數為可選參數
CREATE
[DEFINER = user]
PROCEDURE sp_name ([proc_parameter[,...]])
[characteristic ...] routine_body
-- proc_parameter 參數部分,可以如下書寫:
[ IN | OUT | INOUT ] param_name type
-- type類型可以是MYSQL支持的所有類型
-- toutine_body(程序體)部分,可以書寫合法的SQL語句 ---- BEGIN ... END
2.2.3 routine_body(代碼體)結構
BEGIN
...
END $$
2.3 變量
2.3.1 聲明與賦值
2.3.1.1 declare 聲明
聲明變量 declare var_name type[default var_value]
舉例: declare test_name varchar(32);
2.3.1.2 set 賦值
set test_name = '張三';
2.3.1.3 into賦值
select '張三' into test_name
完整示例
DELIMITER $$
CREATE PROCEDURE test_var_procedure()
BEGIN
declare test_name varchar(32) default '李四';
set test_name = '張三';
select test_name;
END $$
DELIMITER ;
call test_var_procedure();
2.3.2 局部變量與用戶變量
2.3.2.1 局部變量
用戶自定義,在begin-end塊中使用
語法:
聲明變量 declare var_name type[default var_value]
舉例: declare test_name varchar(32);
-- set賦值
DELIMITER $$
CREATE PROCEDURE test_procedure()
BEGIN
declare test_name varchar(32);
set test_name = '張三';
select test_name;
END $$
DELIMITER ;
2.3.2.2 用戶變量
用戶自定義,當前會話(連接)有效
語法:
@test_name
不需要提前聲明,使用即聲明
DELIMITER $$
CREATE PROCEDURE test_procedure()
BEGIN
set @test_name = '張三';
select @test_name;
END $$
DELIMITER ;
call test_procedure();
-- 同一會話中直接查詢也能查詢出來
select @test_name
2.4 入參、出參
2.4.1 入參
IN表示入參,需要傳入到存儲過程中的參數
DELIMITER $$
CREATE PROCEDURE test_procedure(IN age int,IN name varchar(32))
BEGIN
set @age = age;
set @name = name;
select @age,@name;
END $$
DELIMITER ;
call test_procedure(18,'張三');
2.4.2 出參
OUT表示出參
DELIMITER $$
CREATE PROCEDURE test_procedure(IN p_name varchar(32),OUT age int)
BEGIN
SELECT 18 INTO age;
-- set age = 19;
END $$
DELIMITER ;
call test_procedure('張三',@age);
SELECT @age;
2.4.3 出入參
INOUT
DELIMITER $$
CREATE PROCEDURE test_procedure(INOUT age int)
BEGIN
SELECT 18 INTO age;
-- set age = 19;
END $$
DELIMITER ;
-- 通過變量傳參進,再通過同一個變量出
set @age = 20;
call test_procedure(@age);
SELECT @age;
2.5 流程控制
寫流行控制的時候,優先把結構寫好,再補齊內部邏輯,比如寫IF后緊跟着應該先把END IF寫好,邏輯再補在中間,防止遺漏END。
2.5.1 判斷IF
-- 語法
IF condition THEN statement_list
[ELSEIF condition THEN statement_list] ...
[ELSE statement_list]
END IF
示例:
DELIMITER $$
CREATE PROCEDURE test_procedure(IN p_name VARCHAR(32),OUT o_age int)
BEGIN
IF p_name = '張三'
THEN set o_age = 18;
ELSE
set o_age = 19;
END IF;
END $$
DELIMITER ;
call test_procedure('李四',@age);
SELECT @age;
2.5.2 CASE
語義類似於java中的switch..case,CASE也在sql語句中常用到
語法:
-- 語法一
CASE case_value
WHEN when_value THEN statement_list
[WHEN when_value THEN statement_list]
...
[ELSE statement_list]
END CASE
-- 語法二
CASE
WHEN search_condition THEN statement_list
[WHEN search_condition THEN statement_list]
...
[ELSE statement_list]
END CASE
示例:
DELIMITER $$
CREATE PROCEDURE demo_procedure(IN name varchar(32),OUT age INT)
BEGIN
CASE name
WHEN '張三'
THEN set age = 12;
ELSE set age = 18;
END CASE
CASE
WHEN name = '李四'
THEN set age = 20;
ELSE set age = 18;
END CASE;
END $$
DELIMITER ;
call demo_procedure('張三',@age);
select @age;
2.5.3 循環LOOP
-- 語法
[begin_label:] LOOP
statement_list
END LOOP [end_label]
begin_label:給循環聲明一個變量,這個變量指向這個循環體
舉例
需要說明,loop是死循環,需要手動退出循環,我們可以使用leave來退出。
可以把leave看成我們java中的break;與之對應的,就有iterate--類比java的continue
-- 循環打印1-10
DELIMITER $$
CREATE PROCEDURE demo_procedure()
BEGIN
-- 聲明一個下標變量,初始值為1
DECLARE demo_index INT DEFAULT 1;
demo_loop:LOOP
-- 若大於10,則跳出循環
IF demo_index >10
THEN LEAVE demo_loop;
END IF;
select demo_index;
set demo_index = demo_index + 1;
END LOOP demo_loop;
END $$
DELIMITER ;
call demo_procedure();
2.5.4 跳出LEAVE
Leave語句表明退出指定標簽的流程控制語句塊,通常會用在begin…end,以及loop, repeat, while的循環語句
-- 循環打印1-10
DELIMITER $$
CREATE PROCEDURE demo_procedure()
BEGIN
-- 聲明一個下標變量,初始值為1
DECLARE demo_index INT DEFAULT 1;
demo_loop:LOOP
-- 若大於10,則跳出循環
IF demo_index >10
THEN LEAVE demo_loop;
END IF;
select demo_index;
set demo_index = demo_index + 1;
END LOOP demo_loop;
END $$
DELIMITER ;
call demo_procedure();
2.5.5 ITERATE
類比於java中的continue
DELIMITER $$
CREATE PROCEDURE demo_procedure()
BEGIN
-- 打印到10的時候才跳出,攔截打印到2的時候跳出
DECLARE num INT DEFAULT 1;
demo_loop: LOOP
SELECT num;
set num = num +1;
IF num <10
THEN ITERATE demo_loop;
END IF;
IF num =3 or num = 11
THEN LEAVE demo_loop;
END IF;
END LOOP demo_loop;
END $$
DELIMITER ;
call demo_procedure();
2.5.6 REPEAT
類似於java的do..while
-- 語法
[demo_label:] REPEAT
statement_list
UNTIL search_condition -- 直到...為止
END REPEAT [demo_label]
start=>start: start
statement=>operation: statement
expressions=>condition: expressions
stop=>end: stop
start->statement->expressions
expressions(false)->statement
expressions(true)->stop
示例:
DELIMITER $$
CREATE PROCEDURE demo_procedure()
BEGIN
-- 循環打印1-10
DECLARE num INT DEFAULT 1;
demo_repeat: REPEAT
SELECT num;
set num = num + 1;
UNTIL num>10
END REPEAT demo_repeat;
END $$
DELIMITER ;
call demo_procedure();
2.5.7 WHILE
類似於java中的while
-- 語法
[demo_label:] WHILE demo_condition DO
statement_list
END WHILE [demo_lable]
示例:
-- 循環打印1-10
DELIMITER $$
CREATE PROCEDURE demo_procedure()
BEGIN
-- 循環打印1-10
DECLARE num INT DEFAULT 1;
demo_while: WHILE num <=10 DO
select num;
set num = num+1;
END WHILE demo_while;
END $$
DELIMITER ;
call demo_procedure();
2.6 游標
游標實際上是一種能從包括多條數據記錄的結果集中每次提取一條記錄的機制。游標充當指針的作用。盡管游標能遍歷結果中的所有行,但他一次只指向一行。游標的作用就是用於對查詢數據庫所返回的記錄進行遍歷,以便進行相應的操作。
2.6.1 用法
-- 1.聲明一個游標,這里的demo_table可以是任意集合
DECLARE cur_name CURSOR FOR demo_table
-- 2.打開定義的游標
OPEN cur_name
-- 3.獲得下一行數據
FETCH cur_name INTO field_one,field_two,...
-- 4.釋放游標
CLOSE cur_name
2.6.2 示例
-- 建表,造數據
-- name,age
-- 張三,18
-- 李四,19
-- 需求:將表中所有人的姓名和年齡拼接成一個字符串:張三,18;李四,19;
CREATE TABLE `user_information` (
`name` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,
`age` int DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
INSERT INTO user_information(name,age) VALUES ('張三',18);
INSERT INTO user_information(name,age) VALUES ('李四',19);
-- -------------------- 創建存儲過程----------------------
DELIMITER $$
-- 將用戶信息拼接成一個字符串 張三,18;李四,19;
CREATE PROCEDURE demo_procedure()
BEGIN
-- 臨時存儲姓名
DECLARE c_name VARCHAR(255);
-- 臨時存儲年齡
DECLARE c_age INT;
-- 存放拼接字符串
DECLARE res VARCHAR(255) DEFAULT '';
DECLARE done INT DEFAULT 0;
-- 1.定義游標
DECLARE demo_cursor CURSOR FOR select name,age from user_information;
-- 2.捕獲系統拋出的 not found 錯誤,如果捕獲到,將 done 設置為 1 相當於try異常
DECLARE CONTINUE HANDLER FOR NOT found SET done=1;
-- 3.打開游標
OPEN demo_cursor;
demo_loop: LOOP
-- 注意FETCH的位置,FETCH是觸發not found的觸發點,但是done的判斷和java的catch異常有區別,
-- FETCH觸發異常的時候done已經為1了,但是done的IF判斷還沒有執行,要在下一次loop循環執行,所以一定要注意FETCH
-- 和done判斷的位置。
FETCH demo_cursor INTO c_name,c_age;
-- 若done=1,即發生not found異常,則結束循環
IF done=1
THEN LEAVE demo_loop;
END IF;
SET res = CONCAT(res,c_name,',',c_age,';');
END LOOP demo_loop;
SELECT res;
-- 釋放游標(ps:網上很多文章說游標未釋放會產生死鎖,但是我沒有復現-_-)
CLOSE demo_cursor;
END $$
DELIMITER ;
call demo_procedure();
2.7 存儲過程中的HANDLER
HANDLER的聲明必須要放在變量聲明、游標聲明的后面
聲明順序依次為變量、游標、HANDLER
異常抓取
-- 語法
-- 執行順序為,發生異常->抓取到condition_value的異常->執行statement語句->執行handler_action相應的動作
DECLARE handler_action HANDLER
FOR condition_value[,condition_value]...
statement
-- 常見handler_aciton
handler_action:{
CONTINUE|
EXIT|
UNDO
}
-- 常見的condition_value,condition_value也可以是指定的錯誤碼
{
mysql_error_code|
SQLSTATE [VALUE] sqlstate_value|
condition_name|
SQLWARNING|
NOT FOUND|
SQLEXCEPTION
}
-- CONTINUE 繼續往下執行程序
CONTINUE: Execution of the current program continues.
-- EXIT 退出,后面語句不執行
EXIT:Execution terminates for the BEGIN ... END compound statement in which
the handler is declared. This is true even if the condition occurs in an inner block.
-- UNDO 后面語句不執行,前面執行過的語句撤銷(據說mysql還不支持,暫未驗證)