MySQL/儲存程式
MySQL 支援一些對 SQL 的過程擴充套件。透過使用它們,您可以管理控制流、建立迴圈和使用遊標。這些功能允許您建立儲存程式,它們可以是三種類型:
- 觸發器 - 在某個事件涉及表之前/之後觸發的程式(DELETE、INSERT、UPDATE);
- 事件 - 定期在一段時間間隔後執行的程式;
- 儲存過程 - 可以透過 CALL SQL 命令呼叫的程式。
MySQL 的未來版本將支援用其他語言編寫的儲存程式,而不僅僅是 SQL。您將能夠管理新的語言作為 PLUGIN。此外,儲存過程將被編譯成 C 程式碼,因此它們將更快。
觸發器在 MySQL 5.0.2 中新增。它們在持久表上工作,但不能與 TEMPORARY 表相關聯。
建立新的觸發器
CREATE TRIGGER `delete_old` AFTER INSERT ON `articles`
FOR EACH ROW BEGIN
DELETE FROM `articles` ORDER BY `id` ASC LIMIT 1
END
這個示例觸發器定義了一個名為 `delete_old` 的儲存程式(它是一個簡單的 DELETE 語句)。它在將新記錄 INSERT 到 `articles` 時自動觸發。它是在 INSERT 之後呼叫,而不是之前。如果單個 INSERT 將多行新增到表中,`delete_old` 將被呼叫多次。這個想法很簡單:當建立新記錄時,最舊的記錄會被 DELETEd。
觸發器可以在某個 SQL 語句之前或之後執行。這很重要,因為觸發器可以執行一個或多個啟用其他觸發器的語句;因此,決定它們的執行順序可能很重要,以確保資料庫的完整性。
觸發觸發器的語句必須是基本的 DML 命令
- INSERT,包括 LOAD DATA 和 REPLACE
- DELETE,包括 REPLACE,但不包括 TRUNCATE
- UPDATE
特殊情況是 INSERT ... ON DUPLICATE KEY UPDATE。如果執行了 INSERT,則 BEFORE INSERT 和 AFTER INSERT 都會執行。如果 INSERT 未執行,並且因此執行了 UPDATE,則事件順序如下:BEFORE INSERT、BEFORE UPDATE、AFTER UPDATE。
您也可以使用以下語法指定表名
... ON `my_database`.`my_table` ...
觸發器名稱在資料庫中必須是唯一的。同一個資料庫中的兩個表不能關聯到具有相同名稱的兩個不同觸發器。
與其他 DBMS 和標準 SQL 不同,所有觸發器都針對每行觸發,並且不能針對每個語句執行。
儲存程式必須指定在 BEGIN 和 END 保留字之間。您不能在此處使用動態 SQL(PREPARE 語句);相反,可以使用呼叫儲存過程。如果您只執行一條語句,可以省略 BEGIN 和 END 字。
您可以訪問欄位的舊值(它在語句執行之前的價值)以及新值(它在語句執行之後的值)。示例
CREATE TRIGGER `use_values` AFTER INSERT ON `example_tab`
FOR EACH ROW BEGIN
UPDATE `changelog` SET `old_value`=OLD.`field1`, `new_value`=NEW.`field1` WHERE `backup_tab`.`id`=`example_tab`.`id`
END
要 DROP 觸發器,您可以使用以下語法
DROP TRIGGER `my_trigger`
或者
DROP TRIGGER `my_database`.`my_trigger`
或者
DROP TRIGGER IF EXISTS `my_trigger`
要更改現有觸發器,您必須 DROP 並重新建立它。
此命令返回用於建立觸發器的 CREATE TRIGGER 語句,以及有關可能影響語句的設定的一些資訊。
SHOW CREATE TRIGGER delete_old;
- 觸發器 - 觸發器名稱
- sql_mode - 語句執行時的 SQL_MODE 值
- SQL 原始語句
- character_set_client
- collation_connection
- 資料庫排序規則
此語句在 MySQL 5.1 中新增。
如果您想獲取當前資料庫中所有觸發器的列表,可以鍵入以下內容
SHOW TRIGGERS
如果您想獲取另一個數據庫中包含的觸發器的列表,可以使用
SHOW TRIGGERS IN `my_db`
SHOW TRIGGERS FROM `my_db` -- synonym
如果您想列出名稱與 LIKE 表示式匹配的觸發器
SHOW TRIGGERS FROM `my_db` LIKE 'my_%'
更復雜的過濾器
SHOW TRIGGERS WHERE table='users'
您不能將 LIKE 和 WHERE 同時使用。
此語句返回的列為
- 觸發器 - 觸發器的名稱
- 事件 - 觸發觸發器的 SQL 命令
- 表 - 與觸發器關聯的表
- 語句 - 觸發器執行的語句
- 時間 - BEFORE 或 AFTER
- 建立 - 它始終為 NULL
- sql_mode - 建立觸發器時設定的 SQL_MODE
- 定義者 - 建立觸發器的使用者
- character_set_client - 建立觸發器時的 `character_set_client` 變數的值
- collation_connection - 建立觸發器時的 `collation_connection` 變數的值
- 資料庫排序規則 - 資料庫(和觸發器)使用的排序規則
INFORMATION_SCHEMA 虛擬資料庫有一個 `TRIGGERS` 表。它具有以下欄位
- TRIGGER_CATALOG - 哪個目錄包含觸發器(尚未實現)
- TRIGGER_SCHEMA - 哪個 SCHEMA(DATABASE)包含觸發器
- TRIGGER_NAME - 觸發器的名稱
- EVENT_MANIPULATION - INSERT / UPDATE /DELETE
- EVENT_OBJECT_CATALOG - 尚未實現
- EVENT_OBJECT_SCHEMA - 包含與觸發器關聯的表的 SCHEMA
- EVENT_OBJECT_NAME - 與觸發器關聯的表的名稱
- ACTION_ORDER - 尚未實現
- ACTION_CONDITION - 尚未實現
- ACTION_STATEMENT - 觸發器啟用時要執行的語句
- ACTION_ORIENTATION - 尚未實現
- ACTION_TIMING - BEFORE / AFTER
- ACTION_REFERENCE_OLD_TABLE - 未實現
- ACTION_REFERENCE_NEW_TABLE - 未實現
- ACTION_REFERENCE_OLD_ROW - 未實現
- ACTION_REFERENCE_NEW_ROW - 未實現
- 建立 - 建立時間(尚未實現)
- SQL_MODE - 此觸發器執行的有效 SQL_MODE
- 定義者 - 建立觸發器的使用者,格式為 'user@host'
- CHARACTER_SET_CLIENT - 建立觸發器時的 `character_set_client` 變數的值
- COLLATION_CONNECTION - 建立觸發器時的 `collation_connection` 變數的值
- DATABASE_COLLATION - 資料庫(和觸發器)使用的排序規則
事件也稱為計劃事件或時間觸發器。它們是計劃的事件,在特定時間或特定時間間隔內執行。它們類似於 UNIX crontab。
一旦事件啟動,就必須完全執行。如果在事件執行結束之前重新啟用它,則會建立同一個事件的新例項。如果可能發生這種情況,使用鎖來確保資料一致性可能是一個好主意。
事件排程器是一個執行緒,它永久地處於執行狀態。它在事件必須啟動時啟動事件。如果您不需要事件,您可以停用事件排程器。您可以使用以下選項啟動 MySQL 來執行此操作
mysqld --event-scheduler=DISABLED
或者,您可以在 my.cnf 配置檔案中新增一行
event_scheduler=DISABLED
如果事件排程器沒有被停用,您將能夠在執行時將其開啟/關閉。它由一個全域性系統變數控制
SELECT event_scheduler -- values: ON / OFF / DISABLED
SET GLOBAL event_scheduler = ON
SET GLOBAL event_scheduler = OFF
如果事件排程器已開啟,您可以使用 SHOW PROCESSLIST 檢查其狀態。它與所有其他執行緒一樣顯示。它的 `User` 是 'event_scheduler'。當它處於休眠狀態時,`State` 的值為 'Waiting for next activation'。
管理事件
[edit | edit source]您可以使用 SQL 命令 CREATE EVENT、ALTER EVENT 和 DROP EVENT。
CREATE EVENT
[edit | edit source]最簡單的情況。我們希望在明天執行一個 SQL 命令
CREATE EVENT `newevent`
ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 1 DAY
DO
INSERT INTO `mydatabase`.`news` (`title`, `text`) VALUES ('Example!', 'This is not a reale news')
事件名稱必須在 "EVENT" 後面指定。
如果您想建立一個僅在特定時間執行一次的任務,則需要使用 AT 子句。如果您不想指定一個絕對時間,而是希望在經過一段時間間隔後執行任務,則 "AT CURRENT_TIMESTAMP + INTERVAL ..." 是一種有用的語法。
如果您想建立一個定期任務(將在固定時間間隔內執行),則需要使用 EVERY 子句
CREATE EVENT `newevent2`
ON SCHEDULE EVERY 2 DAY
DO
OPTIMIZE TABLE `mydatabase`.`news`
您還可以指定開始時間和/或結束時間。任務將在固定時間間隔內從開始時間執行到結束時間
CREATE EVENT `newevent2`
ON SCHEDULE EVERY INTERVAL 1 DAY
DO
OPTIMIZE TABLE `mydatabase`.`news`
STARTS CURRENT_TIMESTAMP + 1 MONTH
ENDS CURRENT_TIMESTAMP + 3 MONTH
允許的時間單位是
YEAR, QUARTER, MONTH, WEEK, DAY, HOUR, MINUTE, SECOND, YEAR_MONTH, DAY_HOUR, DAY_MINUTE, DAY_SECOND, HOUR_MINUTE, HOUR_SECOND, MINUTE_SECOND
DO 子句指定必須執行哪個語句。
如果一個任務由多個語句組成,則必須使用 BEGIN ... END 語法
delimiter |
CREATE EVENT `newevent`
ON SCHEDULE
EVERY 1 DAY
DO
BEGIN
DELETE FROM `logs`.`user` WHERE `deletion_time` < CURRENT_TIMESTAMP - 1 YEAR;
DELETE FROM `logs`.`messages` WHERE `deletion_time` < CURRENT_TIMESTAMP - 1 YEAR;
UPDATE `logs`.`activity` SET `last_cleanup` = CURRENT_TIMESTAMP;
END |
delimiter ;
如果具有相同名稱的 EVENT 已經存在,您將收到來自伺服器的錯誤。要抑制錯誤,您可以使用 IF NOT EXISTS 子句
CREATE EVENT `newevent2`
IF NOT EXISTS
ON SCHEDULE EVERY 2 DAY
DO
OPTIMIZE TABLE `mydatabase`.`news`
在 EVENT 過期後(當 AT 子句或 ENDS 子句中指定的的時間戳過期時),MySQL 會預設刪除事件,因為它不再有用。您可能希望將其保留下來,以便將來進行 ALTER 並重新啟用它,或者只是將其程式碼儲存在某個地方。您可以使用 ON COMPLETION 子句來執行此操作
CREATE EVENT `newevent2`
ON SCHEDULE EVERY 2 DAY
ON COMPLETION PRESERVE
DO
OPTIMIZE TABLE `mydatabase`.`news`
或者,您可以明確告訴 MySQL 刪除它,即使沒有必要
CREATE EVENT `newevent2`
ON SCHEDULE EVERY 2 DAY
ON COMPLETION NOT PRESERVE
DO
OPTIMIZE TABLE `mydatabase`.`news`
如果您沒有告訴 MySQL 在 EVENT 過期後保留它,但它在建立後立即過期(如果在 AT / ENDS 子句中指定了過去的 TIMESTAMP,則會發生這種情況),伺服器會按照您的要求建立並刪除它。但是,在這種情況下,它會返回 1588 警告以通知您。
您還可以指定 EVENT 是否必須啟用。這可以透過指定 ENABLE、DISABLE 或 DISABLE ON SLAVES(用於在主伺服器上執行事件,而不是在從伺服器上覆制它)來完成。預設情況下,EVENT 已啟用。
CREATE EVENT `newevent2`
ON SCHEDULE EVERY 2 DAY
ON COMPLETION NOT PRESERVE
DISABLE
DO
OPTIMIZE TABLE `mydatabase`.`news`
要修改此行為,您將使用 ALTER EVENT。
您可以為 EVENT 指定一個註釋。註釋的字元限制為 64 個字元。註釋必須是文字,而不是表示式。例如
CREATE EVENT `newevent2`
ON SCHEDULE EVERY 2 DAY
ON COMPLETION NOT PRESERVE
DISABLE
COMMENT 'let\'s optimize some tables!'
DO
OPTIMIZE TABLE `mydatabase`.`news`
您還可以指定在執行 EVENT 時必須使用哪個使用者來檢查許可權。預設情況下,使用 CURRENT_USER。您可以明確指定它
CREATE DEFINER = CURRENT_USER
EVENT `newevent2`
ON SCHEDULE EVERY 2 DAY
DO
OPTIMIZE TABLE `mydatabase`.`news`
要指定不同的使用者,您必須具有 SUPER 許可權。在這種情況下,您必須同時指定使用者名稱和主機
CREATE DEFINER = 'allen@localhost'
EVENT `newevent2`
ON SCHEDULE EVERY 2 DAY
DO
OPTIMIZE TABLE `mydatabase`.`news`
ALTER EVENT
[edit | edit source]ALTER EVENT 語句可用於修改現有 EVENT。
CREATE EVENT `newevent2`
ON SCHEDULE EVERY 2 DAY
ON COMPLETION NOT PRESERVE
RENAME TO `example_event`
DISABLE
COMMENT 'let\'s optimize some tables!'
DO
OPTIMIZE TABLE `mydatabase`.`news`
RENAME TO 用於重新命名 EVENT。
您只需要指定要更改的子句
CREATE EVENT `newevent2` ENABLE;
DROP EVENT
[edit | edit source]您需要 EVENT 許可權才能刪除事件。要刪除事件,您可以輸入
DROP EVENT `event_name`
如果 EVENT 不存在,您會收到 1517 錯誤。要避免這種情況,您可以使用 IF EXISTS 子句
DROP EVENT IF EXISTS `event_name`
如果 EVENT 只需執行一次或只是在已知的時間段內執行,MySQL 預設會在 EVENT 過期時自動刪除它(請參閱 CREATE EVENT 中的 ON COMPLETE 子句)。
元資料
[edit | edit source]SHOW CREATE EVENT
[edit | edit source]此命令返回用於建立觸發器的 CREATE EVENT 語句,以及有關可能影響語句的設定的一些資訊。
語法
SHOW CREATE EVENT newevent2;
- Event - 事件名稱。
- sql_mode - 執行 CREATE EVENT 語句時生效的 SQL 模式。
- time_zone - 執行語句時使用的時區。
- Create Event - 用於建立事件的語句。
- character_set_client
- collation_connection
- 資料庫排序規則
SHOW EVENTS
[edit | edit source]該語句顯示有關當前資料庫或指定資料庫中 EVENT 的資訊
SHOW EVENTS
SHOW EVENTS FROM `my_nice_db`
SHOW EVENTS IN `my_nice_db` -- synonym
SHOW EVENTS LIKE 'my_%' -- name starts with 'my_'
SHOW EVENTS WHERE definer LIKE 'admin@%' -- filters on any field
- Db 資料庫名稱。
- Name 事件名稱。
- Definer 建立 EVENT 的使用者以及他使用的主機,格式為 user@host。
- Time zone EVENT 使用的時區。如果從未更改,它應該為 'SYSTEM',這意味著:伺服器的時區。
- Type 'ONE TIME' 表示僅執行一次的 EVENT,'RECURRING' 表示定期執行的 EVENT。
- Executed At EVENT 將執行的時刻的 TIMESTAMP。對於遞迴 EVENT,為 NULL。
- Interval Value EVENT 執行之間的時間間隔數。請參閱下一個欄位。對於僅執行一次的 EVENT,為 NULL。
- Interval Field EVENT 執行之間等待的時間間隔型別。例如,如果 `Interval Field` 為 'SECOND' 且 `Interval Value` 為 30,則 EVENT 將每 30 秒執行一次。對於僅執行一次的 EVENT,為 NULL。
- Starts 遞迴 EVENT 的第一次執行 DATETIME。對於僅執行一次的事件,為 NULL。
- Ends 遞迴 EVENT 的最後一次執行 DATETIME。對於僅執行一次的事件,為 NULL。
- Status ENABLED、DISABLED 或 SLAVESIDE_DISABLED。對於 ENABLED 和 DISABLED,請參閱以上內容。SLAVESIDE_DISABLED 是在 5.1 中新增的,表示 EVENT 在主伺服器上已啟用,但在從伺服器上已停用。
- Originator 建立 EVENT 的伺服器的 ID。如果它是在當前伺服器上建立的,則該值為 0。在 5.1 中新增。
- character_set_client
- collation_connection
- 資料庫排序規則
INFORMATION_SCHEMA.EVENTS
[edit | edit source]INFORMATION_SCHEMA 虛擬資料庫有一個 `EVENTS` 表。它是非標準的,是在 5.1 中新增的。EVENTS 具有以下欄位
- EVENT_CATALOG 始終為 NULL(CATALOG 在 MySQL 中未實現)。
- EVENT_SCHEMA 資料庫名稱。
- EVENT_NAME 事件名稱。
- DEFINER 建立 EVENT 的使用者以及他使用的主機,格式為 user@host。
- TIME_ZONE EVENT 使用的時區。如果從未更改,它應該為 'SYSTEM',這意味著:伺服器的時區。
- EVENT_BODY 用於編寫將執行的例程的語言。
- EVENT_DEFINITION 將執行的例程。
- EVENT_TYPE 'ONE TIME' 表示僅執行一次的 EVENT,'RECURRING' 表示定期執行的 EVENT。
- EXECUTE_AT EVENT 將執行的時刻的 TIMESTAMP。對於遞迴 EVENT,為 NULL。
- INTERVAL_VALUE EVENT 執行之間的時間間隔數。請參閱下一個欄位。對於僅執行一次的 EVENT,為 NULL。
- INTERVAL_FIELD EVENT 執行之間等待的時間間隔型別。例如,如果 `Interval Field` 為 'SECOND' 且 `Interval Value` 為 30,則 EVENT 將每 30 秒執行一次。對於僅執行一次的 EVENT,為 NULL。
- SQL_MODE 建立 EVENT 時生效的 SQL 模式。
- STARTS 遞迴 EVENT 的第一次執行 DATETIME。對於僅執行一次的事件,為 NULL。
- ENDS 遞迴 EVENT 的最後一次執行 DATETIME。對於僅執行一次的事件,為 NULL。
- STATUS ENABLED、DISABLED 或 SLAVESIDE_DISABLED。對於 ENABLED 和 DISABLED,請參閱以上內容。SLAVESIDE_DISABLED 是在 5.1 中新增的,表示 EVENT 在主伺服器上已啟用,但在從伺服器上已停用。
- ON_COMPLETION 'NOT PRESERVE'(EVENT 將被刪除)或 'PRESERVE'(EVENT 不會被刪除)。
- CREATED 建立時間 DATETIME。
- LAST_ALTERED 最後一次編輯的 DATETIME。如果 EVENT 從未被修改,則 `LAST_ALTERED` 與 `CREATED` 具有相同的值。
- LAST_EXECUTED 最後一次執行的 TIMESTAMP。如果 EVENT 從未被執行,則該值為 NULL。
- EVENT_COMMENT 與 EVENT 關聯的註釋。如果沒有註釋,則該值為一個空字串。
- ORIGINATOR 建立 EVENT 的伺服器的 ID。如果它是在當前伺服器上建立的,則該值為 0。在 5.1 中新增。
- character_set_client
- collation_connection
- 資料庫排序規則
儲存例程是使用 SQL(帶有一些過程擴充套件)編寫的模組,可以使用 CALL 命令在另一個語句中呼叫。
如果儲存例程返回結果,則稱為函式;如果它們不返回任何內容,則稱為過程。儲存過程不要與在 C 或 LUA 中編寫的過程混淆,這些過程可以在 SELECT 語句中使用;儲存函式不要與 UDF 混淆,即使它們都是使用 CREATE FUNCTION 語句建立的。
- 它們減少了網路流量:它們可能包含許多語句,但只需要傳送一個語句來呼叫它們。
- 能夠將邏輯保留在資料庫中。
- 可重用模組,可以從外部程式呼叫,無論它們是用什麼語言編寫的。
- 您可以修改儲存例程,而無需更改程式。
- 呼叫儲存例程的使用者無需訪問它讀取/寫入的表。
- 呼叫儲存例程比執行單個語句更快。
CREATE DEFINER = `root`@`localhost` PROCEDURE `Module1` ( ) NOT DETERMINISTIC NO SQL SQL SECURITY DEFINER OPTIMIZE TABLE wiki1_page;
CALL `Module1` ();
DROP PROCEDURE `Module1` ;
DROP PROCEDURE `Module1` ;
CREATE DEFINER = `root`@`localhost` PROCEDURE `Module1` ( ) NOT DETERMINISTIC NO SQL SQL SECURITY DEFINER
BEGIN
OPTIMIZE TABLE wiki1_page;
OPTIMIZE TABLE wiki1_user;
END
SHOW PROCEDURE STATUS;
SHOW CREATE PROCEDURE Module1;
虛擬資料庫 INFORMATION_SCHEMA 包含一個名為 `ROUTINES` 的表,其中包含函式和過程資訊。
此表包含所有儲存函式的值。
MySQL 使用一個字元作為分隔符 - MySQL 知道該字元出現的位置,SQL 語句結束,可能另一個語句開始。預設情況下該字元為 ';'。當您建立一個包含多個語句的儲存程式時,您只輸入一個語句:CREATE 命令。但是,它在它的主體中包含多個語句,這些語句用 ';' 分隔。在這種情況下,您需要通知 MySQL ';' 不識別 CREATE 語句的結束:您需要另一個分隔符。
在下面的示例中,'|' 用作分隔符
delimiter |
CREATE EVENT myevent
ON SCHEDULE EVERY 1 DAY
DO
BEGIN
TRUNCATE `my_db`.`my_table`;
TRUNCATE `my_db`.`another_table`;
END
delimiter ;
關鍵字是:IF, CASE, ITERATE, LEAVE LOOP, WHILE, REPEAT[1]。
DELIMITER $$
CREATE PROCEDURE counter()
BEGIN
DECLARE x INT;
SET x = 1;
WHILE x <= 5 DO
SET x = x + 1;
END WHILE;
SELECT x; -- 6
END$$
DELIMITER ;
DELIMITER $$
CREATE PROCEDURE counter2()
BEGIN
DECLARE x INT;
SET x = 1;
boucle1: LOOP
SET x = x + 1;
IF x > 5 THEN
LEAVE boucle1;
END IF;
END LOOP boucle1;
SELECT x; -- 6
END$$
DELIMITER ;
DELIMITER $$
CREATE PROCEDURE counter3()
BEGIN
DECLARE x INT;
SET x = 1;
REPEAT
SET x = x + 1; UNTIL x > 5
END REPEAT;
SELECT x; -- 6
END$$
DELIMITER ;
遊標允許以不同的方式處理每一行,但它會大大降低查詢速度。
DELIMITER $$
CREATE PROCEDURE cursor1()
BEGIN
DECLARE result varchar(100) DEFAULT "";
DECLARE c1 CURSOR FOR
SELECT page_title
FROM wiki1.wiki1_page
WHERE page_namespace = 0;
OPEN c1;
FETCH c1 INTO result;
CLOSE c1;
SELECT result;
END;$$
DELIMITER ;
它們應該在迴圈之前宣告和開啟,該迴圈應該以不同的方式處理每條記錄。要了解表的結束,我們應該在遊標之後建立一個處理程式
-- Concatenate all a table column values on a row
DELIMITER $$
CREATE PROCEDURE cursor2()
BEGIN
DECLARE result varchar(100) DEFAULT "";
DECLARE total text DEFAULT "";
DECLARE done BOOLEAN DEFAULT 0;
DECLARE c2 CURSOR FOR
SELECT page_title
FROM wiki1.wiki1_page
WHERE page_namespace = 0;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN c2;
REPEAT
FETCH c2 INTO result;
set total = concat(total, result);
UNTIL done END REPEAT;
CLOSE c2;
SELECT total;
END;$$
DELIMITER ;
處理程式宣告允許在發生錯誤時指定處理方法[2]
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION
此外,可以指示錯誤型別
DECLARE CONTINUE HANDLER FOR SQLSTATE [VALUE] sqlstate_value
DECLARE CONTINUE HANDLER FOR SQLWARNING
DECLARE CONTINUE HANDLER FOR NOT FOUND