【MySQL】8.0:存儲程序

8.1 存儲程序

存儲程序:
預先在數據庫服務器端存儲SQL命令/語句,並且過後能在數據庫服務器端被執行的數據庫對象。

存儲程序的主體:
存儲程序定義的主體除了常規的SQL語句外,通常還使用變量聲明、選擇、循環和複合語句等。

使用存儲程序:
利用CALL語句等方式使用存儲程序。

存儲程序的分類:
存儲例程:和一個數據庫相關,可以根據名字調用
觸發器:和一個表相關,並在該表產生特殊事件時被觸發
事件:和一個數據庫相關,代表了由MySQL服務器的事件調度器在特定時刻調度執行的任務

8.1.1 存儲例程

存儲例程:存儲例程是存儲在服務器端的SQL語句集合,能夠用存儲例程名字複用相應的代碼
經常用於提高效率和安全性。

  1. 減少在服務器和客戶端之間的數據傳輸。
  2. 對儲存例程的授權管理更易於結合應用系統安全性
  3. 存儲例程很適合記錄日誌

存儲過程PROCEDURE與存儲函數FUNCTION
區別:過程沒有返回值,通過CALL調用;函數通過return返回值

但是存儲過程也能返回“值”甚至結果集。

  1. 允許使用輸出類型的參數來傳遞值
  2. 允許使用select語句,這樣可以返回結果集。

8.1.2 觸發器

觸發器是數據庫的命名對象,與一個表相關聯,並且在該表的INSERT,UPDATE,DELETE等更改操作前後被觸發。

定義觸發器:
where 在哪張表
when 在什麼時候,即由什麼操作觸發
what 被觸發時執行什麼SQL語句。

觸發器的典型應用:

  1. 實現自定義完整性約束
    例如一位教師在一個學期最多隻能承擔三門課程
  2. 用於值的計算
    例如訂單明細發生改變時,重新計算訂單金額並更新相關表中的相關數據
  3. 日誌或副本記錄
    可確保系統跟蹤並審計“時變”數據

8.1.3 事件

和一個數據庫相關,代表了由MySQL服務器的事件調度器在特定時刻調度執行的任務。

定義事件的要素:

  1. 事件的時刻屬性:
    在某時刻僅執行一次
    按照時間間隔週期性執行多次
  2. 事件的任務屬性:
    要執行的SQL語句

事件的典型應用:

  1. 更新彙總報告
  2. 清理過期失效的數據
  3. 歸檔、備份數據

8.2 創建和調用存儲過程

使用CREATE PROCEDURE語句創建存儲過程。
如,創建一個存儲過程,用於備份表記錄到備份表中。

CREATE PROCEDURE backup()
INSERT INTO t_bak SELECT * FROM t;

用CALL調用存儲過程。

mysql>CALL backup();
Query OK,3 rows affected(0.00 sec)

存儲過程的處理需要多條語句的:
使用BEGIN-END
如,歸檔:備份表記錄到備份表中後,刪除原表記錄。

CREATE PROCEDURE backup1()
BEGIN
	INSERT INTO t_bak SELECT * FROM t;
	DELETE FROM t;
END

使用DELIMITER語句界定符

mysql> DELIMITER //
mysql> CREATE PROCEDURE backup1()
    -> BEGIN
    -> INSERT INTO t_bak SELECT * FROM t;
    -> DELETE FROM t;
    -> END//
Query OK, 0 rows affected (0.01 sec)

8.2.1 存儲過程的參數模式

存儲過程的參數類型可以是MySQL的有效數據類型,參數有IN、OUT、INOUT三種。

存儲過程的IN參數
例如,要求存儲過程備份那些主鍵字段值小於給定值的記錄。

CREATE PROCEDURE backup3(n int)
BEGIN
	INSERT INTO t_bak SELECT * FROM t WHERE id<=n;
	DELETE FROM t WHERE id<=n;
END

存儲過程的OUT參數
例如修改backup3,使之傳回本次備份的數據數

CREATE PROCEDURE backup3(n int, OUT record_count INT)
BEGIN
	INSERT INTO t_bak SELECT * FROM t WHERE id<=n;
	SELECT COUNT(*) INTO record_count FROM t WHERE id<=n;
	# 返回存儲過程的結果集
	DELETE FROM t WHERE id<=n;
END

8.2.2 存儲過程的安全上下文

有權執行某個存儲過程的用戶在執行存儲過程時,存儲過程中的SQL語句的執行,按定義者(默認)或調用者的權限進行檢查。

定義者:默認是執行CREATE PROCEDURE 語句的用戶,也可以用DEFINER子句指定另外的用戶名
調用者:執行CALL語句的用戶

8.3 創建和調用存儲函數

用CREATE FUNCTION 來創建存儲函數

CREATE FUNCTION sp([func_parameter[,...]]) RETURNS type
// 存儲函數必須說明返回值的類型
routine_body //存儲函數必須有return 返回值

func_parameter:parameter name //參數視作in參數

函數調用:
直接使用函數名+參數調用,類似SQL內置函數,存儲函數的調用可以出現在很多位置。

實例:

CREATE FUNCTION backup3(n int) RETURNS int
BEGIN
	DECLARE record_count int;
	INSERT INTO t_bak SELECT * FROM t WHERE id<=n;
	SELECT COUNT(*) INTO record_count FROM t WHERE id<=n;
	# 返回存儲過程的結果集
	DELETE FROM t WHERE id<=n;
	RETURN record_count;
END

8.4 存儲過程和存儲函數的共性

存儲例程特性的說明:

characteristic:
| [NOT] DETERMINISTIC
| {NO SQL| CONTAINS SQL| READS SQL DATA| MODIFIES SQL DATA}
| SQL SECURITY {DEFINER | INVOKER}

存儲例程的確定性:
[NOT] DETERMINISTIC,確定性,即給定同樣的輸入參數是否總能得到相同的輸出。例如內置函數NOW()就是不確定性的。

默認選項是NOT DETERMINISTIC,該選項會對優化產生影響,因爲如果是確定性的,那麼MySQL可能會使用緩存等優化手段。

存儲例程的數據訪問特性:

| {NO SQL| CONTAINS SQL| READS SQL DATA| MODIFIES SQL DATA},默認爲CONTAINS SQL

NO SQL:存儲例程不包含SQL語句
CONTAINS SQL:存儲例程不包含讀或寫的SQL語句
READS SQL DATA:存儲例程僅讀取數據,例如SELECT,但是不能寫數據
MODIFIES SQL DATA:存儲例程可以寫數據,如INSERT

8.5 存儲例程的維護管理

8.5.1 查看存儲例程

SHOW PROCEDURE STATUS [like_or_where]
SHOW FUNCTION STATUS [like_or_where]

例如:

mysql>SHOW PROCEDURE STATUS LIKE 'backup'\G
// 不使用分號結尾而是反斜槓+G,使結果以縱向方式輸出方便查看

mysql>SHOW CREATE PROCEDURE 'backup'\G
// 與上文的區別在於會多返回創建存儲例程時的語句。

8.5.2 刪除存儲例程

DROP PROCEDURE [IF EXISTS] proc_name;
DROP FUNCTION [IF EXISTS] func_name;

存儲例程刪除後不可恢復,刪除不存在的存儲例程會報錯。

8.5.3 修改存儲例程

如何修改存儲例程的定義?先刪除已創建的存儲例程,然後重新定義新的存儲例程。

如何修改存儲例程的特性:使用ALTER語句修改

ALTER PROCEDURE proc_name [characteristic...];
ALTER FUNCTION func_name [characteristic...]

characteristic:
COMMENT 'string'
| LANGUAGE SQL
| {NO SQL| CONTAINS SQL| READS SQL DATA| MODIFIES SQL DATA}
| SQL SECURITY {DEFINER | INVOKER}

授權執行存儲例程:
用GRANT語句授權用戶執行存儲例程。
用戶執行存儲例程需要存儲例程對象上的EXECUTE權限

GRANT EXECUTE ON [{PROCEDURE | FUNCTION }]
	{*.*| db_name.* |db_name.routine_name} TO user

db_name.routine_name:授權指定的數據庫對象.存儲例程名給指定對象
db_name.* :使用通配符,在數據庫層面將數據庫內所有存儲例程都授權給用戶
*.* :全局權限,用戶可以執行服務器上的所有數據庫的所有存儲例程。

8.5.4 在存儲程序中使用遊標

存儲程序中對結果集每行記錄依次處理,需要使用遊標(CURSOR)。

遊標的作用
• 在存儲程序中編程訪問SELECT所返回結果集
• 方便逐行訪問並對每行記錄完成相應的處理

遊標的使用

  1. 先聲明:DECLARE CURSOR和DECLARE HANDLER
  2. 後使用
    OPEN,使用遊標必須先顯式打開遊標
    FETCH,提取當前行記錄字段值
    CLOSE,最後關閉遊標

8.5.5 在存儲過程中使用事務

MySQL默認在每一條SQL語句執行後都自動提交(事務),也允許在存儲過程中使用顯式地事務控制。

事務控制原則
• 根據需要手工啓動事務
• 根據處理情況(成功時)提交事務或(失敗時)回滾事務

常用的事務控制語句
• START TRANSACTION ——用於啓動事務
• COMMIT ——用於提交事務
• ROLLBACK ——用於回滾事務

實例:

用存儲過程transfer實現轉賬功能,其中包括事務處理。

CREATE PROCEDURE transfer(account_from INT, account_to INT, amount INT, OUT status INT)
MODIFIES SQL DATA
BEGIN
	DECLARE account_from_balance INT;
	DECLARE EXIT HANDLER FOR SQLEXCEPTION BEGIN ROLLBACK; SET status=-1; END;
	START TRANSACTION;
	UPDATE bank_account SET Balance = Balance + amount WHERE Account_Id = account_to;
	SELECT balance INTO account_from_balance FROM bank_account WHERE Account_Id = account_from;
	IF account_from_balance < amount THEN
		ROLLBACK; SET status = -1;
	ELSE
		UPDATE bank_account SET Balance = Balance - amount WHERE Account_Id = account_from;
		COMMIT; SET status = 0;
	END IF;
END

8.6 觸發器相關

觸發器是數據庫的命名對象,與一個表相關聯,並且在該表的INSERT,UPDATE,DELETE等更改操作前後被觸發。

使得適合讓表的增刪改操作接受一定的強制性規則。

8.6.1 創建觸發器

CREATE TRIGGER trigger_name 
// 創建觸發器
{ BEFORE | AFTER }{INSERT | UPDATE| DELETE}
// 觸發的時機與事件
ON tb_name FOR EACH ROW
// 觸發器關聯的表
trigger_body
//觸發器主體定義

觸發器示例1:將無效成績“舍入”到有效成績

CREATE TRIGGER valid_score_before_update_choose
BEFORE UPDATE
ON choose FOR EACH ROW
BEGIN
	IF New.Score < 0 THEN SET New.Score = 0;
	ELSEIF New.Score > 100 THEN SET New.Score = 100;
	END IF;
END

觸發器示例2:拒絕無效成績

CREATE TRIGGER valid_score_before_update_choose
BEFORE UPDATE
ON choose FOR EACH ROW
BEGIN
	IF New.Score < 0 Or New.Score > 100 THEN
		SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT ='Score must be in [0, 100]';
	END IF;
END

觸發器示例3:使用觸發器保證每位教師最多開設3門課程

CREATE TRIGGER teacher_courses_constraint
BEFORE INSERT
ON course FOR EACH ROW
BEGIN
	IF (SELECT COUNT(*) FROM course WHERE Teacher_id = New.Teacher_id) >= 3 THEN
		SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'at most 3 courses for a teacher';
	END IF;
END

8.6.2 查看觸發器的定義

查看觸發器列表
SHOW TRIGGERS [{FROM | IN} db_name] [LIKE 'pattern' | WHERE expr]

mysql> SHOW TRIGGERS\G
*************************** 1. row ***************************
Trigger: valid_score_before_update_choose
Event: UPDATE
Table: choose
Statement: BEGIN
IF New.Score < 0 Or New.Score > 100 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Score must be in [0, 100]';
END IF;
END
Timing: BEFORE
......
查看定義存儲例程的CREATE語句

mysql> SHOW CREATE TRIGGER valid_score_before_insert_choose\G
*************************** 1. row ***************************
Trigger: valid_score_before_update_choose
......
SQL Original Statement: CREATE DEFINER=`root`@`localhost`
TRIGGER valid_score_before_insert_choose
BEFORE UPDATE
ON choose FOR EACH ROW
BEGIN
IF New.Score < 0 Or New.Score > 100 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Score must be in [0, 100]';
END IF;
END
......

8.6.3 刪除觸發器

使用DROP TRIGGER語句刪除觸發器
DROP TRIGGER [IF EXISTS] [schema_name.]trigger_name

如:DROP TRIGGER IF EXISTS valid_score_before_update_choose;

注意:
修改表名後,該表上的觸發器仍然有效;
刪除表後,該表上創建的觸發器會自動被刪除。

8.7 事件相關

事件是指在MySQL事件調度器的調度下,在特定的時刻所執行的任務,因此也稱爲調度事件。

事件調度器配置:
全局變量event_scheduler代表事件調度器的狀態

SHOW VARIABLES LIKE 'event_scheduler';//查看事件調度器
//其值可以爲ON、OFF或DISABLED,代表啓動、停止和禁用。

可使用命令行參數或my.ini配置’event_scheduler’=DISABLED,這樣事件調度器將被禁用且運行時不可改變狀態

而未配置爲DISABLED 時,可以通過:

SET GLOBAL event_scheduler = ON(或OFF

在運行時啓動或停止事件調度器。

8.7.1 事件的概念和基本屬性

事件也是一種存儲程序,是事件調度器按照時間和間隔爲依據,調度執行事件的任務代碼。
事件也有和其它存儲程序相似的屬性:

存儲程序的共性屬性:
名稱、屬於哪個數據庫、要執行的SQL語句。

和觸發器相似的屬性:
定義者(類似於觸發器,有定義者,沒有調用者)

事件所特有的屬性:
調度的時間和週期(類似於觸發器的觸發事件)

調度的時間和週期:

  1. 在什麼時間調度
    • 僅調度一次的任務在什麼時間
    • 重複調度的事件,首次調度在什麼時間

  2. 每隔多長時間重複調度
    是否需要在某個時間後就不再重複調度

  3. 過期的事件是否要自動刪除

8.7.2 創建事件

使用CREATE EVENT語句創建事件

CREATE EVENT event_name
// 創建事件
ON SCHEDULE
// 調度事件的時機
{ AT time_spec 
// 一次性事件的時刻
| EVERY interval [STARTS time_spec] [ENDS time_spec] }
// 重複事件的週期和始終
[ ON COMPLETION [NOT] PRESERVE ]
// 完成後是否保留
[ ENABLE | DISABLE ]
// 創建時啓用還是禁用
DO event_body;
// 事件要執行的SQL

示例1:定義一個一次性事件,在一分鐘後備份表。

CREATE EVENT event_backup
ON SCHEDULE
AT CURRENT_TIMESTAMP + INTERVAL 1 MINUTE
DO INSERT INTO t_bak SELECT * FROM t;

解釋:
• 在默認數據庫中創建一個事件,名爲event_backup
• 事件是一個一次性事件(使用了AT子句)
• 事件在當前時間(CREATE EVENT語句執行時)後1分鐘被調度
• 事件接受兩個默認選項:創建後啓用,完成後不予保留
• 事件完成的任務是DO後面的一條語句

示例2:定義一個重複性事件,在每天1點定時備份表。

CREATE EVENT daily_backup
ON SCHEDULE
EVERY 1 DAY STARTS CURRENT_DATE + INTERVAL 1 HOUR
DO INSERT INTO t_bak SELECT * FROM t;

解釋:
• 在默認數據庫中創建一個事件,名爲daily_backup
• 事件是一個重複性事件(使用了EVERY子句)
• 事件在的運行週期爲1天(EVERY 1 DAY)
• 起始時間是當天1點(STARTS CURRENT_DATE + INTERVAL 1 HOUR)
• 事件將持續循環(沒有使用ENDS指定終止時間)

8.7.3 查看、修改與刪除事件

查看事件列表
mysql> SHOW EVENTS\G

查看定義事件的CREATE語句
mysql> SHOW CREATE EVENT daily_backup\G
使用ALTER EVENT語句
• 不需要先刪除後重新創建
• 語句中的成分和CREATE EVENT非常相似
• 增加了RENAME TO子句,用於修改事件名稱
舉例:
ALTER EVENT daily_backup
ON SCHEDULE
EVERY 1 WEEK STARTS CURRENT_DATE + INTERVAL 2 HOUR
RENAME TO weekly_backup
使用DROP EVENT語句刪除事件
• DROP EVENT [IF EXISTS] [schema_name.]event_name

舉例:
DROP EVENT IF EXISTS weekly_backup;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章