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的錯誤處理機制如下:
- 如果BEFORE型觸發器執行失敗,相應行的操作也不會被執行。
- BEFORE型觸發器是有對行的插入或修改行為啟用,無論後續的插入或修改是否成功。
- 只有當所有的BEFORE型觸發器和所有的行操作全部執行成功,AFTER型觸發器才被執行。
- 如果BEFORE或AFTER觸發器在執行過程中出現錯誤,將導致呼叫觸發器的整個SQL語句執行失敗。
- 對於事務性表,觸發器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種方法:
- SHOW VARIABLES LIKE ‘event_scheduler’;
- SELECT @@event_scheduler;
- SHOW PROCESSLIST;
啟事件計劃(定時器)開關有4種方法:
- SET GLOBAL event_scheduler = 1;
- SET @@global.event_scheduler = 1;
- SET GLOBAL event_scheduler = ON;
- 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 計劃任務,有兩種設定計劃任務的方式:
- AT 時間戳,用來完成單次的計劃任務。
- 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