淺談mysql數(shù)據(jù)庫技術(shù),輕松玩轉(zhuǎn)存儲過程

存儲過程新手指南

存儲過程(Stored Procedure)

一組可編程的函數(shù),是為了完成特定功能的SQL語句集,經(jīng)編譯創(chuàng)建并保存在數(shù)據(jù)庫中,用戶可通過指定存儲過程的名字并給定參數(shù)(需要時)來調(diào)用執(zhí)行。

為什么要用存儲過程(優(yōu)點)?

  • 對于一些復(fù)用性高或者業(yè)務(wù)復(fù)雜的一些操作,封裝到一個存儲過程中,避免了重復(fù)編寫SQL造成漏寫或錯寫操作,簡化了SQL的調(diào)用

  • 批量處理:SQL + 循環(huán),減少流量

  • 數(shù)據(jù)遷移,數(shù)據(jù)備份

  • 統(tǒng)一接口,保證數(shù)據(jù)安全(重點,尤其是銀行系統(tǒng))

相對于Oracle數(shù)據(jù)庫來說,Mysql的存儲過程功能相對較弱,運用的較少,在之前的文章中也有提到,Mysql是把存儲過程劃分為函數(shù)體,沒有Oracle那樣細分。

存儲過程的創(chuàng)建與調(diào)用

  • 存儲過程就是有名字的一段代碼,用來完成特定功能的操作

  • 創(chuàng)建的存儲過程保存在數(shù)據(jù)庫的數(shù)據(jù)字典中

創(chuàng)建存儲過程


CREATE

[DEFINER = { user | CURRENT_USER }]

PROCEDURE sp_name ([proc_parameter[,...]])

[characteristic ...] routine_body

proc_parameter:

[ IN | OUT | INOUT ] param_name 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

[begin_label:] BEGIN

[statement_list]

……

END [end_label]

  • 創(chuàng)建數(shù)據(jù)庫,用于示例操作

CREATE DATABASE huafeng_db;

use huafeng_db;

DROP TABLE IF EXISTS `huafeng_db`.`t_scores`;

DROP TABLE IF EXISTS `huafeng_db`.`t_students`;

DROP TABLE IF EXISTS `huafeng_db`.`t_class`;

CREATE TABLE `huafeng_db`.`t_class` (

  `class_id` int(11) NOT NULL,

  `class_name` varchar(32) CHARACTER SET utf8 DEFAULT NULL,

  PRIMARY KEY (`class_id`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `huafeng_db`.`t_class` (`class_id`, `class_name`) VALUES ('1', '一年級');

INSERT INTO `huafeng_db`.`t_class` (`class_id`, `class_name`) VALUES ('2', '二年級');

INSERT INTO `huafeng_db`.`t_class` (`class_id`, `class_name`) VALUES ('3', '三年級');

INSERT INTO `huafeng_db`.`t_class` (`class_id`, `class_name`) VALUES ('4', '四年級');

INSERT INTO `huafeng_db`.`t_class` (`class_id`, `class_name`) VALUES ('5', '五年級');

INSERT INTO `huafeng_db`.`t_class` (`class_id`, `class_name`) VALUES ('6', '六年級');

CREATE TABLE `t_students` (

  `student_id` int(11) NOT NULL AUTO_INCREMENT,

  `student_name` varchar(32) NOT NULL,

  `sex` int(1) DEFAULT NULL,

  `seq_no` int(11) DEFAULT NULL,

  `class_id` int(11) NOT NULL,

  PRIMARY KEY (`student_id`),

  KEY `class_id` (`class_id`),

  CONSTRAINT `t_students_ibfk_1` FOREIGN KEY (`class_id`) REFERENCES `t_class` (`class_id`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `huafeng_db`.`t_students`(`student_name`,`sex`,`seq_no`,`class_id`) VALUES('小紅',0,1,'1');

INSERT INTO `huafeng_db`.`t_students`(`student_name`,`sex`,`seq_no`,`class_id`) VALUES('小青',0,2,'2');

INSERT INTO `huafeng_db`.`t_students`(`student_name`,`sex`,`seq_no`,`class_id`) VALUES('小明',1,3,'3');

INSERT INTO `huafeng_db`.`t_students`(`student_name`,`sex`,`seq_no`,`class_id`) VALUES('小蘭',0,4,'4');

INSERT INTO `huafeng_db`.`t_students`(`student_name`,`sex`,`seq_no`,`class_id`) VALUES('小米',1,5,'5');

INSERT INTO `huafeng_db`.`t_students`(`student_name`,`sex`,`seq_no`,`class_id`) VALUES('小白',1,6,'6');

CREATE TABLE `huafeng_db`.`t_scores` (

  `score_id` int(11) NOT NULL AUTO_INCREMENT,

  `course_name` varchar(64) DEFAULT NULL,

  `score` double(3,2) DEFAULT NULL,

  `student_id` int(11) DEFAULT NULL,

  PRIMARY KEY (`score_id`),

  KEY `student_id` (`student_id`),

  CONSTRAINT `t_scores_ibfk_1` FOREIGN KEY (`student_id`) REFERENCES `t_students` (`student_id`)

) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

INSERT INTO `t_scores` (`score_id`, `course_name`, `score`, `student_id`) VALUES ('1', '語文', '90', '1');

INSERT INTO `t_scores` (`score_id`, `course_name`, `score`, `student_id`) VALUES ('2', '數(shù)學(xué)', '97', '1');

INSERT INTO `t_scores` (`score_id`, `course_name`, `score`, `student_id`) VALUES ('3', '英語', '95', '1');

INSERT INTO `t_scores` (`score_id`, `course_name`, `score`, `student_id`) VALUES ('4', '語文', '92', '2');

INSERT INTO `t_scores` (`score_id`, `course_name`, `score`, `student_id`) VALUES ('5', '數(shù)學(xué)', '100', '2');

INSERT INTO `t_scores` (`score_id`, `course_name`, `score`, `student_id`) VALUES ('6', '英語', '98', '2');

示例:創(chuàng)建一個存儲過程,根據(jù)學(xué)生編號刪除學(xué)生信息


DELIMITER $$ #將語句的結(jié)束符號從分號;臨時改為兩個$$(可以是自定義)

CREATE PROCEDURE p_delstudent_by_no(IN seqNo INT)

BEGIN

DELETE FROM t_students WHERE seq_no = seqNo;

END $$

DELIMITER ; #將語句的結(jié)束符號恢復(fù)為分號

注: 在定義過程中,使用DELIMITER $$ 命令將語句的結(jié)束符號從分號 ; 臨時改為兩個$$,使得過程體中使用的分號被直接傳遞到服務(wù)器,而不會被客戶端(如mysql)解釋。

調(diào)用存儲過程:call sp_name[(傳參)]


mysql> select *from t_students;

+------------+--------------+------+--------+----------+

| student_id | student_name | sex  | seq_no | class_id |

+------------+--------------+------+--------+----------+

|          1 | 小紅        |    0 |      1 |        1 |

|          2 | 小青        |    0 |      2 |        2 |

|          3 | 小明        |    1 |      3 |        3 |

|          4 | 小蘭        |    0 |      4 |        4 |

|          5 | 小米        |    1 |      5 |        5 |

|          6 | 小白        |    1 |      6 |        6 |

+------------+--------------+------+--------+----------+

6 rows in set (0.00 sec)

mysql> call p_delstudent_by_no(2);

Query OK, 1 row affected (0.01 sec)

mysql> select *from t_students;

+------------+--------------+------+--------+----------+

| student_id | student_name | sex  | seq_no | class_id |

+------------+--------------+------+--------+----------+

|          1 | 小紅        |    0 |      1 |        1 |

|          3 | 小明        |    1 |      3 |        3 |

|          4 | 小蘭        |    0 |      4 |        4 |

|          5 | 小米        |    1 |      5 |        5 |

|          6 | 小白        |    1 |      6 |        6 |

+------------+--------------+------+--------+----------+

5 rows in set (0.00 sec)

注: 在存儲過程中設(shè)置了需要傳入的參數(shù)seqNo,在調(diào)用存儲過程時,把2傳參到seqNo,然后執(zhí)行存儲過程的SQL操作。

存儲過程體

  • 存儲過程體包含了在過程調(diào)用時必須執(zhí)行的語句,例如:DML、DDL語句,IF-THEN-ELSE和WHILE-DO語句、聲明變量的DECLARE語句等

  • 過程體格式:以BEGIN開始,以END結(jié)束(可嵌套)


BEGIN

BEGIN

BEGIN

#業(yè)務(wù)操作

END

END

END

注: 每個嵌套塊及其中的每條語句,必須以分號結(jié)束,表示過程體結(jié)束的BEGIN-END塊(又叫做復(fù)合語句compound statement),則不需要分號。

標(biāo)簽化


[begin_label:] BEGIN

[statement_list]

END [end_label]

  • 給每個過程體命名,以增強代碼的可讀性,示例:

label1:BEGIN

label2:BEGIN

label3:BEGIN

#業(yè)務(wù)操作

END label3;

END label2;

END label1

存儲過程參數(shù) (proc_parameter)

在存儲過程創(chuàng)建的時候,參數(shù)可有0到多個,參數(shù)的屬性可分為以下3種:

  • IN 輸入?yún)?shù):表示調(diào)用者向存儲過程傳入值(傳入值可以是字面量或變量)

  • OUT 輸出參數(shù):表示過程向調(diào)用者傳出值(可以返回多個值)(傳出值只能是變量)

  • INOUT輸入輸出參數(shù):既表示調(diào)用者向過程傳入值,又表示過程向調(diào)用者傳出值(值只能是變量)

IN 輸入?yún)?shù)


mysql> DELIMITER $$

mysql> CREATE PROCEDURE p_prinlt(IN num INT)

->  BEGIN

->    SELECT num;

->  END $$

Query OK, 0 rows affected (0.01 sec)

mysql>  DELIMITER ;

mysql> call p_prinlt(2);

+------+

| num  |

+------+

|    2 |

+------+

OUT 輸出參數(shù)


mysql> delimiter $$

mysql> create procedure out_param(out result int)

-> begin

-> set result = 2;

-> select result;

-> end $$

Query OK, 0 rows affected (0.00 sec)

mysql> delimiter ;

mysql> set @result = 1;

Query OK, 0 rows affected (0.00 sec)

mysql> call out_param(@result);

+--------+

| result |

+--------+

|      2 |

+--------+

INOUT 輸入輸出參數(shù)


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 |

+----------+

注: 在設(shè)計傳參時應(yīng)注意確保參數(shù)名字不能與數(shù)據(jù)庫表列名相同,否則在過程體中,參數(shù)名被當(dāng)做列名來處理

  • 作為一個追求完美的人,我們都應(yīng)該做到專詞專用,傳入?yún)?shù)使用 IN ,傳出參數(shù)使用 OUT ,盡量不用INOUT

高手進階

前面我們已經(jīng)初步了解了存儲過程的創(chuàng)建與調(diào)用,但是我們編寫存儲過程的目的是為了解決一些復(fù)用性高或者業(yè)務(wù)復(fù)雜的一些操作;

統(tǒng)一接口,保證數(shù)據(jù)安全等。所以需要使用事務(wù)管理 DML、DDL語句,IF-THEN-ELSE和WHILE-DO語句、聲明變量的DECLARE語句等

全局變量

全局變量可用于初始化一些數(shù)據(jù),作為公共資源,使用方法如下:


mysql> set @num = 2;

mysql> DELIMITER $$

mysql> CREATE PROCEDURE p_prinlt(IN num INT)

->  BEGIN

->    SELECT num;

->  END $$

Query OK, 0 rows affected (0.01 sec)

mysql>  DELIMITER ;

mysql> call p_prinlt(@num);

+------+

| num  |

+------+

|    2 |

+------+

注: 設(shè)置全局參數(shù)名字一定要帶 @ 前綴

聲明語句(DECLARE)

對于一些復(fù)雜的業(yè)務(wù)操作,過程體需要經(jīng)常用到相同的變量時,則需要聲明一個或多個局部變量以滿足業(yè)務(wù)需求,使用方法如下:


DELIMITER $$

CREATE PROCEDURE p_get_classId(IN seqNo int)

BEGIN

    DECLARE classId INT;

    DECLARE result VARCHAR(256) CHARACTER set utf8;#解決中文亂碼問題

set classId = (SELECT class_id FROM t_students WHERE seq_no = seqNo);

SELECT classId;

END $$

事務(wù)機制(TRANSACTION)

為了保證數(shù)據(jù)的安全,以及接口的統(tǒng)一,防止臟數(shù)據(jù)產(chǎn)生,這樣的存儲過程都應(yīng)該使用事務(wù)。

銀行轉(zhuǎn)賬就是其中的經(jīng)典例子,需要雙方都成功才會提交事務(wù),否則回滾(ROLLBACK)

下面我們用之前創(chuàng)建好的數(shù)據(jù)庫作為例子,編寫一個根據(jù)學(xué)生編號刪除學(xué)生的學(xué)習(xí)成績,并把對應(yīng)的學(xué)生的信息刪除,這樣的一個存儲過程。方法如下:


DELIMITER $$

CREATE PROCEDURE p_del_studentInfo_bySeqNo(IN seqNo INT)

BEGIN

    DECLARE studentId INT;

START TRANSACTION;#啟動事務(wù)

set studentId = (SELECT student_id FROM t_students WHERE seq_no = seqNo);

DELETE FROM t_scores WHERE student_id = studentId;

DELETE FROM t_students WHERE student_id = studentId;

COMMIT ;#提交事務(wù)

END $$

DELIMITER ;

條件判斷語句

在存儲過程中,條件判斷語句是必不可少的,經(jīng)常使用的有IF-THEN-ELSE和WHILE-DO語句,條件語句可以減少很多數(shù)據(jù)庫不必要的內(nèi)存開銷,

現(xiàn)在我們來優(yōu)化一下上面寫的刪除學(xué)生信息的存儲過程,代碼如下:


DROP PROCEDURE IF EXISTS `p_del_studentInfo_bySeqNo`;

DELIMITER $$

CREATE PROCEDURE p_del_studentInfo_bySeqNo(IN seqNo INT)

BEGIN

    DECLARE studentId INT;

START TRANSACTION;#啟動事務(wù)

set studentId = (SELECT student_id FROM t_students WHERE seq_no = seqNo);

IF studentId >0 THEN

DELETE FROM t_scores WHERE student_id = studentId;

DELETE FROM t_students WHERE student_id = studentId;

COMMIT ;#提交事務(wù)

ELSE

ROLLBACK;#回滾

END IF;

END $$

DELIMITER ;

注: IF條件判斷語句一定是以 “END IF; ”結(jié)束的,中間可以有多層條件判斷。格式:IF 條件體 THEN 執(zhí)行業(yè)務(wù)結(jié)果 ELSE 執(zhí)行業(yè)務(wù)結(jié)果 END IF

異常處理(SQLEXCEPTION)

每個存儲過程都應(yīng)該有報錯處理的能力,我們還是使用上面的存儲過程進行優(yōu)化,使用方法如下:


DROP PROCEDURE IF EXISTS `p_del_studentInfo_bySeqNo`;

DELIMITER $$

CREATE PROCEDURE p_del_studentInfo_bySeqNo(IN seqNo INT)

BEGIN

    DECLARE studentId INT;

    DECLARE e_code INTDEFAULT 0;#初始化報錯碼為0

    DECLARE result VARCHAR(256) CHARACTER set utf8;#初始化返回結(jié)果,解決中文亂碼問題

DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET e_code=1;#遇到錯誤后繼續(xù)執(zhí)行;(需要返回執(zhí)行結(jié)果時用這個)

START TRANSACTION;#啟動事務(wù)

set studentId = (SELECT student_id FROM t_students WHERE seq_no = seqNo);

IF studentId >0 THEN

DELETE FROM t_scores WHERE student_id = studentId;

DELETE FROM t_students WHERE student_id = studentId;

ELSE

SET e_code = 1;

SET result = '該學(xué)生不存在!';

END IF;

IF e_code=1 THEN

ROLLBACK;  #回滾

ELSE

COMMIT;

SET result = '該學(xué)生已被刪除成功';

END IF;

SELECT result;

END $$

DELIMITER ;

  • 定義報錯代碼變量名并設(shè)置初始化值為0 DECLARE e_code INT DEFAULT 0;

  • 定義化返回結(jié)果變量名并設(shè)置編碼為utf-8 DECLARE result VARCHAR(256) CHARACTER set utf8;

  • DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET e_code=1; 遇到錯誤后繼續(xù)執(zhí)行;(需要返回執(zhí)行結(jié)果時用這個)

  • DECLARE EXIT HANDLER FOR SQLEXCEPTION SET e_code=1;遇到錯誤后退出當(dāng)前塊;

聲明該存儲過程的使用對象(SQL SECURITY INVOKER)

在創(chuàng)建存儲過程的時候,默認是當(dāng)前用戶可使用所有權(quán)限,其他用戶不能使用或者不能執(zhí)行存儲過程中的DML語句,為了使其他用戶也能使用該存儲過程

則需要在創(chuàng)建的時候聲明一下,使用方法如下:


DROP PROCEDURE IF EXISTS `p_del_studentInfo_bySeqNo`;

DELIMITER $$

CREATE PROCEDURE p_del_studentInfo_bySeqNo(IN seqNo INT)

SQL SECURITY INVOKER  #允許其他用戶運行

BEGIN

    DECLARE studentId INT;

    DECLARE e_code INTDEFAULT 0;#初始化報錯碼為0

    DECLARE result VARCHAR(256) CHARACTER set utf8;#初始化返回結(jié)果,解決中文亂碼問題

DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET e_code=1;#遇到錯誤后繼續(xù)執(zhí)行;(需要返回執(zhí)行結(jié)果時用這個)

START TRANSACTION;#啟動事務(wù)

set studentId = (SELECT student_id FROM t_students WHERE seq_no = seqNo);

IF studentId >0 THEN

DELETE FROM t_scores WHERE student_id = studentId;

DELETE FROM t_students WHERE student_id = studentId;

ELSE

SET e_code = 1;

SET result = '該學(xué)生不存在!';

END IF;

IF e_code=1 THEN

ROLLBACK;  #回滾

ELSE

COMMIT;

SET result = '該學(xué)生已被刪除成功';

END IF;

SELECT result;

END $$

DELIMITER ;

拓展

習(xí)慣性建議

  • 默認情況下,存儲過程和默認數(shù)據(jù)庫相關(guān)聯(lián),如果想指定存儲過程創(chuàng)建在某個特定的數(shù)據(jù)庫下,那么在過程名前面加數(shù)據(jù)庫名做前綴;

  • 代碼盡量簡潔,有序,該縮進的地方使用縮進

  • 過程體有必要的時候盡量采用標(biāo)簽

  • 存儲過程傳參數(shù)時盡量使用對應(yīng)的名詞,盡量不用INOUT

  • 每次創(chuàng)建表或者函數(shù)和存儲過程的時候盡量先判斷是否存在再刪除以免造成報錯,采用 DROP … IF EXISTS …格式

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 自行創(chuàng)建一個員工信息表info,里面記錄著員工姓名,年齡,地址以及工資,下面的實驗要使用。 mysql> crea...
    張偉科閱讀 1,495評論 0 2
  • 任務(wù)需求:定時執(zhí)行的任務(wù),調(diào)用存儲過程,進行數(shù)據(jù)遷移。 存儲過程相關(guān)總結(jié):(存儲過程的創(chuàng)建 不能伴隨有if exi...
    時待吾閱讀 3,215評論 0 4
  • 本人做銷售10年,現(xiàn)和一批創(chuàng)業(yè)、銷售高手組建了一個銷售人脈圈,交流Q群 386039402 驗證:簡書。銷售、創(chuàng)業(yè)...
    輕窈瘦閱讀 1,752評論 0 3

友情鏈接更多精彩內(nèi)容