MySQL 之 存储过程
一、初識存儲過程
1、什么是存儲過程
存儲過程是在大型數據庫系統中一組為了完成特定功能的SQL語句集,存儲在數據庫中。存儲過程經過第一次編譯后,再次調用不需要編譯,用戶可以通過指定的存儲過程名和給出一些存儲過程定義的參數來使用它。一般用的較少,和腳本有類似之處。
Java,Python,PHP等應用程序可以調用存儲過程。自MySQL 5.0版本以來,存儲過程,存儲函數,觸發器和事件這些功能才被添加到MySQL數據庫引擎
2、為什么要用存儲過程
程序分兩種,一種是基于web,一種是基于桌面,他們都和數據庫進行交互來完成數據的存取工作。假設現在有一種應用程序包含了這兩種,現在要修改其中的一個查詢sql語句,那么我們可能要同時修改他們中對應的查詢sql語句,當我們的應用程序很龐大很復雜的時候問題就出現這,不易維護!另外把sql查詢語句放在我們的web程序或桌面中很容易遭到sql注入的破壞。而存儲過程正好可以幫我們解決這些問題。
3、存儲過程優缺點
優點
缺點
存儲過程能不用盡量不用。原則是:業務邏輯不要封裝在數據庫里面(數據庫去進行邏輯判斷業務)。把業務邏輯要交給應用程序處理。這樣可以減少數據庫資源消耗。人員也難以招聘,因為既懂存儲過程,又懂業務的人少。使用困難。大量業務邏輯封裝在存儲過程中,造成后面根本就不能動了。動a影響b。以后業務邏輯很難剝離出來。增加以后維護困難
4、存儲過程和函數
相同點
不同點
二、存儲過程的創建
創建存儲過程
注:創建相同名字的存儲過程不會成功,即不能覆蓋一個已經存在的存儲過程。可以先刪除然后再創建。
CREATE[DEFINER = { user | CURRENT_USER }]PROCEDURE sp_name ([proc_parameter[,...]])[characteristic ...] routine_bodyproc_parameter:[ IN | OUT | INOUT ] param_name typecharacteristic: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[begin_label:] BEGIN[statement_list]……END [end_label]MYSQL 存儲過程中的關鍵語法
注:如果在命令行模式下進行存儲過程創建,需要修改語句結束符,避免沖突。使用工具可以不修改語句結束符,示例如下
DELIMITER $$ 或 DELIMITER //聲明存儲過程:
CREATE PROCEDURE demo_in_parameter(IN p_in int)存儲過程開始和結束符號:
BEGIN .... END變量賦值:
SET @p_in=1變量定義:
DECLARE l_int int unsigned default 4000000;下面是存儲過程的例子,刪除給定球員參加的所有比賽:
mysql> delimiter $$ # 將語句的結束符號從分號;臨時改為兩個$$(可以是自定義) mysql> CREATE PROCEDURE delete_matches(IN p_playerno INTEGER)-> BEGIN-> DELETE FROM MATCHES->???WHERE playerno = p_playerno;-> END$$ Query OK, 0 rows affected (0.01 sec) mysql> delimiter; # 將語句的結束符號恢復為分號解析:默認情況下,存儲過程和默認數據庫相關聯,如果想指定存儲過程創建在某個特定的數據庫下,那么在過程名前面加數據庫名做前綴。 在定義過程時,使用 DELIMITER $$ 命令將語句的結束符號從分號 ; 臨時改為兩個 $$,使得過程體中使用的分號被直接傳遞到服務器,而不會被客戶端(如mysql)解釋。
存儲過程體
存儲過程體包含了在過程調用時必須執行的語句,例如:dml、ddl語句,if-then-else和while-do語句、聲明變量的declare語句等
過程體格式:以begin開始,以end結束(可嵌套)
BEGINBEGINBEGINstatements;ENDEND END注意:每個嵌套塊及其中的每條語句,必須以分號結束,表示過程體結束的begin-end塊(又叫做復合語句compound statement),則不需要分號。
如果過程體中只有一條指令,則可以省略 begin 和 end,存儲過程體中的每條sql語句的結尾要求必須加分號。
為語句塊貼標簽
[begin_label:] BEGIN[statement_list] END [end_label]例如:
label1: BEGINlabel2: BEGINlabel3: BEGINstatements;END label3 ;END label2; END label1標簽有兩個作用:
- 增強代碼的可讀性
- 在某些語句(例如:leave和iterate語句),需要用到標簽
捕獲異常
declare continue handler for SQLEXCEPTION set e=1;三、存儲過程的參數
MySQL存儲過程的參數用在存儲過程的定義,共有三種參數類型,IN,OUT,INOUT,形式如:
CREATE PROCEDURE 存儲過程名([[IN |OUT |INOUT ] 參數名 數據類形...])IN 輸入參數:表示調用者向過程傳入值(傳入值可以是字面量或變量)OUT 輸出參數:表示過程向調用者傳出值(可以返回多個值)(傳出值只能是變量)INOUT 輸入輸出參數:既表示調用者向過程傳入值,又表示過程向調用者傳出值(值只能是變量)1、in 輸入參數
mysql> delimiter $$ mysql> create procedure in_param(in p_in int)-> begin-> select p_in;-> set p_in=2;->???select P_in;-> end$$ mysql> delimiter ; mysql> set @p_in=1; # 用戶變量命名最好加@ mysql> call in_param(@p_in); +------+ | p_in | +------+ |??? 1 | +------+ +------+ | P_in | +------+ |??? 2 | +------+mysql> select @p_in; +-------+ | @p_in | +-------+ |???? 1 | +-------+ # 以上可以看出,p_in 在存儲過程中被修改,但并不影響 @p_id 的值,因為前者為局部變量、后者為全局變量。2、out輸出參數
mysql> delimiter // mysql> create procedure out_param(out p_out int)->?? begin->???? select p_out;->???? set p_out=2;->???? select p_out;->?? end-> // mysql> delimiter ; mysql> set @p_out=1; mysql> call out_param(@p_out); +-------+ | p_out | +-------+ |? NULL | +-------+ # 因為out是向調用者輸出參數,不接收輸入的參數,所以存儲過程里的p_out為null+-------+ | p_out | +-------+ |???? 2 | +-------+mysql> select @p_out; +--------+ | @p_out | +--------+ |????? 2 | +--------+ # 調用了out_param存儲過程,輸出參數,改變了p_out變量的值3、inout輸入參數
mysql> delimiter $$ mysql> create procedure inout_param(inout p_inout int)->?? begin->???? select p_inout;->???? set p_inout=2;->???? select p_inout;->?? end-> $$ mysql> delimiter ; mysql> set @p_inout=1; mysql> call inout_param(@p_inout); +---------+ | p_inout | +---------+ |?????? 1 | +---------+ +---------+ | p_inout | +---------+ |?????? 2 | +---------+ mysql> select @p_inout; +----------+ | @p_inout | +----------+ |??????? 2 | +----------+ # 調用了inout_param存儲過程,接受了輸入的參數,也輸出參數,改變了變量注意:
1、如果過程沒有參數,也必須在過程名后面寫上小括號 例:
CREATE PROCEDURE sp_name ([proc_parameter[,...]]) ……2、確保參數的名字不等于列的名字,否則在過程體中,參數名被當做列名來處理
四、變量
1. 變量定義
局部變量聲明一定要放在存儲過程體的開始:
DECLARE variable_name [,variable_name...] datatype [DEFAULT value];其中,datatype 為 MySQL 的數據類型,如: int, float, date,varchar(length),例如:
DECLARE l_int int unsigned default 4000000;? DECLARE l_numeric number(8,2) DEFAULT 9.95;? DECLARE l_date date DEFAULT '1999-12-31';? DECLARE l_datetime datetime DEFAULT '1999-12-31 23:59:59';? DECLARE l_varchar varchar(255) DEFAULT 'This will not be padded';2. 變量賦值
SET 變量名 = 表達式值 [,variable_name = expression ...]3.使用SELECT …INTO語句為變量賦值
在MySQL存儲過程中,可以使用SELECT …INTO語句對變量進行賦值,該語句在數據庫中進行查詢,并將得到的結果賦值給變量。SELECT …INTO語句的語法格式如下:
SELECT col_name[,...] INTO var_name[,...] table_exprcol_name:要從數據庫中查詢的列字段名;var_name:變量名,列字段名按照在列清單和變量清單中的位置對應,將查詢得到的值賦給對應位置的變量;table_expr:SELECT語句中的其余部分,包括可選的FROM子句和WHERE子句。需要注意的是,在使用SELECT …INTO語句時,變量名不能和數據表中的字段名相同,否則會出錯。范例語句:
create procedure getMsg?()? Begindeclare v_title varchar(30);?declare v_content varchar(100);?select title,content into v_title,v_content from news where artId=333;? End?將變量值返回給調用者
在存儲過程中定義的變量,經過一系列的處理之后,結果值可能需要返回給存儲過程調用者。那么如何返回呢?方便的做法是使用SELECT語句將變量作為結果集返回,因此,在上面一段代碼的基礎上,加上一句:
create procedure getMsg?()? Begindeclare v_title varchar(30);?declare v_content varchar(100);?select title,content into v_title,v_content from news where artId=333;?select v_title,v_content;? End4. 用戶變量
在MySQL客戶端使用用戶變量
mysql > SELECT 'Hello World' into @x;? mysql > SELECT @x;? +-------------+? |?? @x??????? |? +-------------+? | Hello World |? +-------------+? mysql > SET @y='Goodbye Cruel World';? mysql > SELECT @y;? +---------------------+? |???? @y????????????? |? +---------------------+? | Goodbye Cruel World |? +---------------------+?mysql > SET @z=1+2+3;? mysql > SELECT @z;? +------+? | @z?? |? +------+? |? 6?? |? +------+在存儲過程中使用用戶變量
mysql > CREATE PROCEDURE GreetWorld( ) SELECT CONCAT(@greeting,' World');? mysql > SET @greeting='Hello';? mysql > CALL GreetWorld( );? +----------------------------+? | CONCAT(@greeting,' World') |? +----------------------------+? |? Hello World?????????????? |? +----------------------------+在存儲過程間傳遞全局范圍的用戶變量
mysql> CREATE PROCEDURE p1()?SET @last_procedure='p1';? mysql> CREATE PROCEDURE p2() SELECT CONCAT('Last procedure was ',@last_procedure);? mysql> CALL p1( );? mysql> CALL p2( );? +-----------------------------------------------+? | CONCAT('Last procedure was ',@last_proc?????? |? +-----------------------------------------------+? | Last procedure was p1???????????????????????? |? +-----------------------------------------------+注意:用戶變量名一般以@開頭,濫用用戶變量會導致程序難以理解及管理
五、注釋
MySQL 存儲過程可使用兩種風格的注釋
- 兩個橫桿--:該風格一般用于單行注釋。
- c 風格: 一般用于多行注釋。
六、MySQL存儲過程的調用
用call和你過程名以及一個括號,括號里面根據需要,加入參數,參數包括輸入參數、輸出參數、輸入輸出參數。調用存儲過程示例:
call sp_name[(傳參)];七、MySQL存儲過程的查詢
我們像知道一個數據庫下面有那些表,我們一般采用 showtables; 進行查看。那么我們要查看某個數據庫下面的存儲過程,是否也可以采用呢?答案是,我們可以查看某個數據庫下面的存儲過程,但是是另一種方式。我們可以用以下語句進行查詢:
# 查看所有的存儲過程 select name from mysql.proc where type='PROCEDURE'; # 當然也可以指定數據庫名來縮小范圍 select name from mysql.proc where type='PROCEDURE' and db='數據庫名';# select routine_name from information_schema.routines where routine_schema='數據庫名';# 顯示數據庫中所有存儲的存儲過程基本信息,包括所屬數據庫,存儲過程名稱,創建時間等 show procedure status [where db='數據庫名'];如果我們想知道,某個存儲過程的詳細,那我們又該怎么做呢?是不是也可以像操作表一樣用describe 表名進行查看呢?答案是:我們可以查看存儲過程的詳細,但是需要用另一種方法:
SHOW CREATE PROCEDURE 數據庫.存儲過程名;就可以查看當前存儲過程的詳細。
八、MySQL存儲過程的修改
ALTER PROCEDURE修改存儲過程只能修改那些選項(這里不講解那些具體選項,想了解的可以自行百度),并不能修改傳入傳出參數或者sql語句
更改用 CREATE PROCEDURE 建立的預先指定的存儲過程,其不會影響相關存儲過程或存儲功能。
九、MySQL存儲過程的刪除
刪除一個存儲過程比較簡單,和刪除表一樣:
語法:drop procedure 存儲過程名 #示例DROP PROCEDURE p1;#錯誤演示,不支持批量刪除DROP PROCEDURE p2,p3;十、MySQL存儲過程的控制語句
(1). 變量作用域
內部的變量在其作用域范圍內享有更高的優先權,當執行到 end 變量時,內部變量消失,此時已經在其作用域外,變量不再可見了,應為在存儲過程外再也不能找到這個申明的變量,但是你可以通過 out 參數或者將其值指派給會話變量來保存其值。
(2). 條件語句
1. if-then-else 語句
if 條件 then語句; else語句; end if;if 條件 then語句; elseif 條件 then語句; ..... else語句; end if;示例:?
mysql > DELIMITER //? mysql > CREATE PROCEDURE proc2(IN parameter int)?-> begin-> declare var int;?-> set var=parameter+1;?-> if var=0 then-> insert into t values(17);?-> end if;?-> if parameter=0 then-> update t set s1=s1+1;?-> else-> update t set s1=s1+2;?-> end if;?-> end;?-> //? mysql > DELIMITER ;2. case語句:
case [變量名]when [值] then[執行內容]when [值] then[執行內容]... else[執行內容] end case;示例:?
mysql > DELIMITER //? mysql > CREATE PROCEDURE proc3 (in parameter int)?-> begin-> declare var int;?-> set var=parameter+1;?-> case var?-> when 0 then??-> insert into t values(17);?-> when 1 then??-> insert into t values(18);?-> else??-> insert into t values(19);?-> end case;?-> end;?-> //? mysql > DELIMITER ; casewhen var=0 theninsert into t values(30);when var>0 thenwhen var<0 thenelse end case(3). 循環語句
1. while ···· end while
while語句,先判斷后運行
while 條件 do--循環體 endwhile示例:
mysql > DELIMITER //? mysql > CREATE PROCEDURE proc4()?-> begin-> declare var int;?-> set var=0;?-> while var<6 do?-> insert into t values(var);?-> set var=var+1;?-> end while;?-> end;?-> //? mysql > DELIMITER ;2. repeat···· end repea
它在執行操作后檢查結果,而 while 則是執行前進行檢查。
repeat--循環體 until 循環條件? end repeat;示例:
mysql > DELIMITER //? mysql > CREATE PROCEDURE proc5 ()?-> begin??-> declare v int;?-> set v=0;?-> repeat?-> insert into t values(v);?-> set v=v+1;?-> until v>=5?-> end repeat;?-> end;?-> //? mysql > DELIMITER ;3. loop ·····endloop
loop 循環不需要初始條件,這點和 while 循環相似,同時和 repeat 循環一樣不需要結束條件, leave 語句的意義是離開循環。
loop語句,運行直到遇到leave
[標簽名]:loop[執行內容]leave [標簽名][執行內容] end loop;示例
mysql > DELIMITER //? mysql > CREATE PROCEDURE proc6 ()?-> begin-> declare v int;?-> set v=0;?-> LOOP_LABLE:loop?-> insert into t values(v);?-> set v=v+1;?-> if v >=5 then-> leave LOOP_LABLE;?-> end if;?-> end loop;?-> end;?-> //?mysql > DELIMITER ;4. LABLES 標號:
標號可以用在 begin repeat while 或者 loop 語句前,語句標號只能在合法的語句前面使用。可以跳出循環,使運行指令達到復合語句的最后一步。
(4). ITERATE迭代,相當于continue,LEAVE 結束,相當于break
ITERATE 通過引用復合語句的標號,來從新開始復合語句:
LEAVE 結束循環
mysql > DELIMITER //? mysql > CREATE PROCEDURE proc10 ()?-> begin-> declare v int;?-> set v=0;?-> LOOP_LABLE1:loop?-> if v=3 then??-> set v=v+1;?-> ITERATE LOOP_LABLE1;?# 進行下一次循環-> end if;?-> insert into t values(v);?-> set v=v+1;?-> if v>=5 then-> leave LOOP_LABLE1;?# 結束循環-> end if;?-> end loop;?-> end;?-> //? mysql > DELIMITER ;十一、mysql 在存儲過程中輸出日志信息
1、直接用select 打印輸出
SELECT 'Comment';2、用concat連接變量輸出
declare myvar INT default 0; SET myvar = 5; SELECT concat('myvar is ', myvar); 輸出: myvar is 53、額外創建一個有一列文本列的表,然后往里面塞信息
declare myvar INT default 0; SET myvar = 5; insert into tmptable select concat('myvar is ', myvar); 將上面的sql語句封裝成一個存儲過程log,以后要用的話就直接調用下面的語句就可以了CALL log(concat('the value is', myvar)); 直接輸出到一個文本里面select "penguin" as log into outfile '/tmp/result.txt'; 這個命令會有嚴格的限制,只能將輸出文本放在本地,然后給予其創建和寫的權限 一旦輸出了一個文本,無法重寫,這樣可以阻止惡意執行sql注入十一、DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE?
在MySQL的存儲過程中經常會看到這句話:DECLARE CONTINUE HANDLER FOR NOT FOUND。
它的含義是:若沒有數據返回,程序繼續,并將變量 done 設為TRUE ,這種情況是出現在select XX into XXX from tablename的時候發生的。
我們在使用儲存過程中使用游標遍歷數據的時候的基本寫法如下:
create procedure proc_test() sql security invoker begindeclare p_id varchar(32);declare done tinyint default false;declare c_cur cursor for select id from user;declare continue handler for not found set done = true;open c_cur;fetch c_cur into p_id;while !done do... #程序邏輯fetch c_cur into p_id;end while;close c_cur; end;正常情況這么寫是沒問題的,可是如果你在while里面的要是有select語句的話就有問題了。如果說你的處理邏輯是這樣的:
while !done doselect * from user_role r where r.user_id = p_id;fetch c_cur into p_id; end while;那么當你的select * from user_role r where r.user_id = p_id;找不到數據的時候,declare continue handler for not found set done = true;這句就會執行,有done = true,所以循環體會提前跳出。通過測試得出,declare continue handler for not found set done = true 是對全局的select有效的,只要有一條select語句返回空,那么就是觸發該語句。
解決方法就是確保while里面的select永遠不會返回空
select * from user_role r where r.user_id = p_id;#改成下面這樣 select col1, col2, ... from (select col1, col2, ... from user_role r where r.user_id = p_id union all select '' col1, '' col2, ...) t這樣的話就可以保證select肯定不是空集合。
?
?
總結
以上是生活随笔為你收集整理的MySQL 之 存储过程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 微图App有哪些实用的功能?
- 下一篇: 如何下载矢量二维电子地图数据