1. 程式人生 > >MySQL觸發器與定時器的介紹和錯誤處理

MySQL觸發器與定時器的介紹和錯誤處理

MySQL觸發器與定時器的介紹和錯誤處理方法

最近在做一個東南亞的海外專案,整個專案的技術架構是由我負責,由於專案比較龐大,涉及三種語言,資料關係比較複雜,用的觸發器、定時器比較多。借這個新型大專案,也重溫了了很久沒有接觸的觸發器(TRIGGER)、定時器(EVENT),本文也是回憶結合專案實際的總結篇,希望寫出來對大家有用。


我們知道,從功能上,SQL 語言可以分為三類:
DDL(Data Definition Language):CREATE/ALTER/DROP TABLE
DML(Data Manipulation Language):SELECT/INSERT/DELETE/TRUNCATE/UPDATE
DCL(Data Control Language):GRANT/REVOKE

  • MySQL觸發器(TRIGGER)
  • MySQL定時器(EVENT)
  • MySQL觸發器、定時器的錯誤處理

MySQL觸發器(TRIGGER)

MySQL中,觸發器是一種與資料表事件相關的特殊形式的儲存過程,是建在表上的命名資料庫物件,觸發器常用於加強資料的完整性約束和複雜的業務規則等。

先說說觸發器的特點:

  • 建在表上:觸發器定義在特定的資料表上,這個表可叫做觸發器表。觸發器可以查詢其他表,引用其他表的欄位,可以包含複雜的SQL語句。觸發器所在表必須為永久性表,不能將觸發程式與TEMPORARY表或檢視關聯起來。

  • 由事件觸發:不同於儲存過程,觸發器不能手動呼叫,也不能接收、傳送引數。必須對錶上INSERT、UPDATE 或 DELETE 操作定義了觸發器,然後對應地執行 INSERT、UPDATE 或 DELETE 操作時,該觸發器才可被啟用,觸發程式自動執行。

  • 可呼叫儲存過程:為響應資料庫的變化,觸發器可以呼叫一個或多個儲存過程,保證資料完整性、一致性。

觸發器語法

CREATE TRIGGER trigger_name trigger_time trigger_event     
ON tbl_name 
FOR EACH ROW 
trigger_stmt

trigger_name 觸發器名稱,命名方式同MySQL中其他物件一樣。
trigger_time 觸發時間,可為BEFORE|AFTER
trigger_event 觸發事件, 可為INSERT|UPDATE|DELETE
tbl_name 觸發器所在的表名,同一表的同一事件不能有兩個觸發器。
trigger_stmt 所要觸發的SQL語句。

刪除觸發器:

DROP TRIGGER [IF EXISTS] [schema_name.]trigger_name

說明:從MySQL 5.0.2起,CREATE SCHEMA可作為CREATE DATABASE的代名詞,上述DROP語句的schema_name可以省略,省略則預設從當前資料庫刪除觸發器。由於MySQL 5.0.10名稱空間的改變,當從MySQL 5.0升級到5.0.10及更高版本時,必須刪除觸發器後重新建立它們,否則升級後就無法刪除觸發器了。升級時可以將觸發器匯出後再匯入建立。簡單講,這裡可以將schema(模式)理解為database(資料庫)或namespace(名稱空間)。

實際上,如下兩個資料庫/模式查詢語句是一樣的:

SHOW DATABASES;

SELECT SCHEMA_NAME,DEFAULT_CHARACTER_SET_NAME,DEFAULT_COLLATION_NAME 
FROM INFORMATION_SCHEMA.SCHEMATA;

使用OLD和NEW關鍵字,可以訪問觸發程式影響的記錄中的列。在INSERT觸發程式中,僅能使用NEW.col_name,沒有舊行。在DELETE觸發程式中,僅能使用OLD.col_name,沒有新行。在UPDATE觸發程式中,可以使用OLD.col_name來引用更新前的某一記錄行的列,也可使用NEW.col_name來引用更新後的記錄行中的列。

用OLD命名的列是隻讀的。你可以引用它,但不能更改它。對於用NEW命名的列,如果具有SELECT許可權,可引用它。在BEFORE觸發程式中,如果你具有UPDATE許可權,可使用“SET NEW.col_name = value”更改它的值。這意味著,你可以使用觸發程式來更改將要插入到新行中的值,或用於更新行的值。

在BEFORE觸發程式中,AUTO_INCREMENT列的NEW值為0,不是實際插入新記錄時將自動生成的序列號。

通過使用BEGIN … END結構,能夠定義執行多條語句的觸發程式。在BEGIN…END塊中,還能使用儲存過程的其他語法,如條件判斷和迴圈語句。此時,需要重新定義SQL語句分隔符,以便能夠在觸發程式中使用SQL結束符“;”。

如果不願意為使用SQL複合語句重新定義SQL語句分隔符DELIMITER,或者當多個觸發器使用的觸發程式SQL一樣,最好將觸發程式SQL獨立出來,定義為儲存過程,然後在觸發器中使用CALL語句呼叫該儲存過程。

MySQL 觸發器在操作事務性和非事務性表的區別

對於事務性表,觸發器在執行過程中若有一個觸發程式執行失敗,那麼整個觸發程式將回滾。對於非事務性表,如果後面部分語句執行失敗,但在這之前執行的語句依然有效,無法撤銷。

在觸發器執行過程中,MySQL的錯誤處理機制如下:

  1. 如果BEFORE型觸發器執行失敗,相應行的操作也不會被執行。
  2. BEFORE型觸發器是有對行的插入或修改行為啟用,無論後續的插入或修改是否成功。
  3. 只有當所有的BEFORE型觸發器和所有的行操作全部執行成功,AFTER型觸發器才被執行。
  4. 如果BEFORE或AFTER觸發器在執行過程中出現錯誤,將導致呼叫觸發器的整個SQL語句執行失敗。
  5. 對於事務性表,觸發器SQL語句如果執行失敗,那麼由此執行引起的所有改變都將回滾。

MySQL資料庫引擎預設為MyISAM,不支援事務和外來鍵,InnoDB可支援事務和外來鍵。

檢視當前資料庫支援的引擎以及預設資料庫引擎:

SHOW ENGINES;

建立時設定資料庫引擎:

CREATE TABLE `tab1` (
  `id` int(13) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  `coin` int(10) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

建立後修改資料庫引擎:

方法一:修改MySQL配置檔案

MySQL/MariaDB 預設配置檔案位於 /etc/my.cnf。
我手動安裝的目錄為/usr/local/mysql/my.cnf。
故修改/usr/local/mysql/my.cnf,在[mysqld]下面新增:

default-storage-engine=INNODB

每次更改此配置檔案後一定要重啟 MySQL 服務,以使更改生效。

方法二:通過SQL語句修改

ALTER TABLE table_name ENGINE=MyISAM;

注意:從MySQL5.2起不再支援ENGINE =engine_name的同義語法TYPE = engine_name。
對ENGINE修改後檢視修改結果,使用語句:

SHOW TABLE STATUS FROM database_name;

SHOW CREATE TABLE table_name;

建立觸發器示例

建立兩個表,表名為tab1,tab2。

CREATE TABLE `tab1` (
  `id` int(13) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `coin` int(10) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=0 DEFAULT CHARSET=utf8;

CREATE TABLE `tab2` (
  `id` int(13) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `coin` int(10) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=0 DEFAULT CHARSET=utf8;

在tab1中建立一個觸發器

DROP TRIGGER IF EXISTS t_afterinsert_on_tab1;
CREATE TRIGGER `t_afterinsert_on_tab1` 
AFTER INSERT ON `tab1` 
FOR EACH ROW 
BEGIN

     insert into tab2(id,name,coin) values(new.id,new.name,new.coin+10);

END;

執行報錯提示沒有超級許可權:

[SQL]
DROP TRIGGER IF EXISTS t_afterinsert_on_tab1;
[Err] 1419 - You do not have the SUPER privilege and binary logging is enabled (you *might* want to use the less safe log_bin_trust_function_creators variable)

經查是log_bin_trust_function_creators值為off導致。
執行查詢語句set global log_bin_trust_function_creators=1;也報錯如下:
1227 - Access denied; you need (at least one of) the SUPER privilege(s) for this operation
要更改此設定依然是許可權不足,由於我的mysql授權賬戶為非root許可權,無法更改此設定。具體解決方法參見下文。

若有許可權,則會順利執行成功。如下:

[SQL]DROP TRIGGER IF EXISTS t_afterinsert_on_tab1;
受影響的行: 0
時間: 0.001s

[SQL]

CREATE TRIGGER t_afterinsert_on_tab1 

AFTER INSERT ON tab1

FOR EACH ROW

BEGIN

     insert into tab2(id,name,coin) values(new.id,new.name,new.coin+10);

END;
受影響的行: 0
時間: 0.003s

MySQL定時器(EVENT)

MySQL 5.1引入了事件排程器 Event Scheduler(定時器),所以我們可以通過建立任務定時器,去迴圈執行儲存過程/觸發器,更方便的操作業務資料。

檢視儲存過程或函式的建立程式碼:

SHOW CREATE PROCEDURE proc_name;
SHOW CREATE FUNCTION func_name;

檢視資料庫儲存過程的方法:

SELECT `name`, `body` FROM mysql.proc WHERE `db` = 'db_name' and type = 'PROCEDURE';

SHOW PROCEDURE STATUS;

前者要求更高的許可權。許可權的問題解決參照下文。

檢視事件(定時器):

SHOW EVENTS;
SELECT * FROM mysql.event;

刪除事件(定時器):

DROP EVENT [IF EXISTS] event_name

注:當一個事件(定時器)正在執行時,刪除該事件不會導致事件立即停止,事件會執行完畢才停止。使用DROP USER和DROP DATABASE 語句同時會將包含其中的事件(定時器)刪除。

檢視當前是否已開啟事件計劃(定時器)有3種方法:

  1. SHOW VARIABLES LIKE ‘event_scheduler’;
  2. SELECT @@event_scheduler;
  3. SHOW PROCESSLIST;

啟事件計劃(定時器)開關有4種方法:

  1. SET GLOBAL event_scheduler = 1;
  2. SET @@global.event_scheduler = 1;
  3. SET GLOBAL event_scheduler = ON;
  4. SET @@global.event_scheduler = ON;

說明:鍵值1或者ON表示開啟;0或者OFF表示關閉。如果是ON已開啟狀態,則依次開始執行儲存過程、定時器。

MySQL觸發器、定時器的錯誤處理

MySQL 5.1+開始支援定時器EVENT,要使定時起作用 的話,MySQL的常量GLOBAL event_scheduler必須為on或者是1。

開啟定時器許可權不足

執行

SHOW VARIABLES LIKE '%sche%'; 

檢視是否開啟定時器。

若未開啟請先開啟,執行

SET GLOBAL event_scheduler = 1;

開啟定時器時報錯,提示許可權不足:

[Err] 1227 - Access denied; you need (at least one of) the SUPER privilege(s) for this operation

其他錯誤情形

以下情形和上述類似,大多都是許可權問題所致。

情形一:

執行

SELECT HOST,USER,PASSWORD,Event_priv FROM mysql.user;

SELECT CURRENT_USER(), SCHEMA();

錯誤提示:

[Err] 1142 - SELECT command denied to user 'cashify'@'172.16.60.36' for table 'user'

情形二:

執行

UPDATE mysql.user SET Event_priv = 'Y' WHERE HOST='%' AND USER='cashify';

錯誤提示:

[Err] 1142 - UPDATE command denied to user 'cashify'@'172.16.60.36' for table 'user'

情形三

執行

SELECT `name` FROM mysql.proc WHERE `db` = 'cashify' and `type` = 'PROCEDURE'

錯誤提示:

[Err] 1142 - SELECT command denied to user 'cashify'@'172.16.60.36' for table 'proc'

情形四

執行

SELECT * FROM mysql.event;

錯誤提示

[Err] 1142 - SELECT command denied to user 'cashify'@'172.16.60.36' for table 'event'

許可權不足的解決方法

通過資料庫所在Linux伺服器,以root許可權登入MySQL,然後再執行上述需要執行的SQL語句。

mysql  -uroot -password
SELECT CURRENT_USER(), SCHEMA();
show databases;
use cashify;
show tables;
SELECT HOST,USER,PASSWORD,Event_priv FROM mysql.user;
UPDATE mysql.user SET Event_priv = 'Y' WHERE HOST='%' AND USER='cashify';
SET @@global.event_scheduler = 1;
SELECT @@event_scheduler;

建立事件(定時器)的語法如下:

CREATE EVENT [IF NOT EXISTS]  event_name 
ON SCHEDULE schedule
[ON COMPLETION [NOT] PRESERVE]
[ENABLE | DISABLE]
[COMMENT 'comment'] 
DO sql_statement

修改事件(定時器)語法類似:

ALTER EVENT event_name 
ON SCHEDULE schedule
[RENAME TO new_event_name]
[ON COMPLETION [NOT] PRESERVE]
[ENABLE | DISABLE]
[COMMENT 'comment']
DO sql_statement

解釋說明:

ON SCHEDULE 計劃任務,有兩種設定計劃任務的方式:

  1. AT 時間戳,用來完成單次的計劃任務。
  2. EVERY單位時間數 時間單位 [STARTS 時間戳] [ENDS時間戳],用來完成重複的計劃任務。

在兩種計劃任務中,時間戳可以是任意的TIMESTAMP 和DATETIME 資料型別,時間戳需要大於當前時間。在重複的計劃任務中,單位時間數可以是任意非空(Not Null)的整數,時間單位可以為這些關鍵詞:YEAR,MONTH,DAY,HOUR,MINUTE 或者SECOND。不建議使用除此之外的其他非標準時間單位。

ON COMPLETION引數表示”當這個事件不會再發生的時候”,即當單次計劃任務執行完畢後或當重複性的計劃任務執行到了ENDS階段。而PRESERVE的作用是使事件在執行完畢後不會被Drop掉,建議使用該引數,以便於檢視EVENT具體資訊。

引數ENABLE和DISABLE表示設定事件(定時器)的狀態。ENABLE表示系統將執行這個事件。DISABLE表示系統不執行該事件。

COMMENT表示註釋。註釋會出現在元資料中,儲存在information_schema表的COMMENT列,最大長度為64個位元組。’comment’表示將註釋內容放在單引號之間。

DO sql_statement欄位表示該EVENT需要執行的SQL語句或儲存過程。這裡的SQL語句可以是複合語句。

使用BEGIN和END識別符號將複合SQL語句按照執行順序。事件(定時器)中對SQL語句的限制條件,跟函式Function和觸發器Trigger 中對SQL語句的限制是一樣的,如果某些SQL語句在函式Function 和觸發器Trigger 中不能使用,同樣的在EVENT中也不能使用。明確的來說有下面幾個:

LOCK TABLES
UNLOCK TABLES
CREATE EVENT
ALTER EVENT
LOAD DATA

可以使用如下命令開啟或關閉事件(定時器):

ALTER EVENT event_name [ON COMPLETION PRESERVE] ENABLE/DISABLE

MySQL定時器(EVENT)示例

這是專案中使用者領取任務後的處理邏輯,應用任務到時未完成即實時回收的定時器語句:

-- MySQL 5.1+開始支援定時器EVENT
-- 要使定時起作用 MySQL的常量GLOBAL event_scheduler必須為on或者是1 
-- 檢視是否開啟定時器 
-- SHOW VARIABLES LIKE '%sche%';  
-- 任務30分鐘未完成則自動回收
-- select round((select unix_timestamp())-10 ) as currenttime;
-- SELECT * FROM cash_user_task WHERE updatetime<=round((select unix_timestamp())-1800) AND status=1;

DELIMITER |
DROP PROCEDURE IF EXISTS cashify_timeline_check |
CREATE PROCEDURE cashify_timeline_check()
    BEGIN
    UPDATE cash_user_task SET status=0 WHERE updatetime<=round((select unix_timestamp())-1800) AND status=1;
    UPDATE cash_task SET ammount=amount+1;
    END |
DELIMITER ;
CREATE EVENT IF NOT EXISTS event_cashify_timeline_check 
ON SCHEDULE EVERY 1 MINUTE 
ON COMPLETION PRESERVE   
DO CALL cashify_timeline_check();


-- select unix_timestamp();
-- select current_timestamp, current_timestamp();
-- SELECT TO_DAYS(now());
-- select version();
-- SELECT AUTO_INCREMENT FROM information_schema.tables WHERE table_name="cash_user";
-- ALTER TABLE cash_user auto_increment=10000;
-- SELECT LAST_INSERT_ID()
-- SELECT @@IDENTITY
-- SELECT RAND();
-- SELECT UUID();
-- SELECT * FROM cash_user_task ORDER BY UUID()  limit 10

以上就是關於觸發器(TRIGGER)、定時器(EVENT)的介紹,以及許可權不足的解決方法。涉及MySQL的許可權檢視、分配、收回,請自行了解,不再詳述。


參考文章

升級 MySQL http://imysql.cn/node/74/
MySQL Triggers http://www.w3resource.com/mysql/mysql-triggers.php