8.1 存儲程序
存儲程序:
預先在數據庫服務器端存儲SQL命令/語句,並且過後能在數據庫服務器端被執行的數據庫對象。
存儲程序的主體:
存儲程序定義的主體除了常規的SQL語句外,通常還使用變量聲明、選擇、循環和複合語句等。
使用存儲程序:
利用CALL語句等方式使用存儲程序。
存儲程序的分類:
存儲例程:和一個數據庫相關,可以根據名字調用
觸發器:和一個表相關,並在該表產生特殊事件時被觸發
事件:和一個數據庫相關,代表了由MySQL服務器的事件調度器在特定時刻調度執行的任務
8.1.1 存儲例程
存儲例程:存儲例程是存儲在服務器端的SQL語句集合,能夠用存儲例程名字複用相應的代碼
經常用於提高效率和安全性。
- 減少在服務器和客戶端之間的數據傳輸。
- 對儲存例程的授權管理更易於結合應用系統安全性
- 存儲例程很適合記錄日誌
存儲過程PROCEDURE與存儲函數FUNCTION
區別:過程沒有返回值,通過CALL調用;函數通過return返回值
但是存儲過程也能返回“值”甚至結果集。
- 允許使用輸出類型的參數來傳遞值
- 允許使用select語句,這樣可以返回結果集。
8.1.2 觸發器
觸發器是數據庫的命名對象,與一個表相關聯,並且在該表的INSERT,UPDATE,DELETE等更改操作前後被觸發。
定義觸發器:
where 在哪張表
when 在什麼時候,即由什麼操作觸發
what 被觸發時執行什麼SQL語句。
觸發器的典型應用:
- 實現自定義完整性約束
例如一位教師在一個學期最多隻能承擔三門課程 - 用於值的計算
例如訂單明細發生改變時,重新計算訂單金額並更新相關表中的相關數據 - 日誌或副本記錄
可確保系統跟蹤並審計“時變”數據
8.1.3 事件
和一個數據庫相關,代表了由MySQL服務器的事件調度器在特定時刻調度執行的任務。
定義事件的要素:
- 事件的時刻屬性:
在某時刻僅執行一次
按照時間間隔週期性執行多次 - 事件的任務屬性:
要執行的SQL語句
事件的典型應用:
- 更新彙總報告
- 清理過期失效的數據
- 歸檔、備份數據
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所返回結果集
• 方便逐行訪問並對每行記錄完成相應的處理
遊標的使用
- 先聲明:DECLARE CURSOR和DECLARE HANDLER
- 後使用
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語句。
和觸發器相似的屬性:
定義者(類似於觸發器,有定義者,沒有調用者)
事件所特有的屬性:
調度的時間和週期(類似於觸發器的觸發事件)
調度的時間和週期:
-
在什麼時間調度
• 僅調度一次的任務在什麼時間
• 重複調度的事件,首次調度在什麼時間 -
每隔多長時間重複調度
是否需要在某個時間後就不再重複調度 -
過期的事件是否要自動刪除
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;