最近在做一个东南亚的海外项目,整个项目的技术架构是由我负责,由于项目比较庞大,涉及三种语言,数据关系比较复杂,用的触发器、定时器比较多。借这个新型大项目,也重温了了很久没有接触的触发器(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中,触发器是一种与数据表事件相关的特殊形式的存储过程,是建在表上的命名数据库对象,触发器常用于加强数据的完整性约束和复杂的业务规则等。
建在表上:触发器定义在特定的数据表上,这个表可叫做触发器表。触发器可以查询其他表,引用其他表的字段,可以包含复杂的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数据库引擎默认为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 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种方法:
启事件计划(定时器)开关有4种方法:
说明:键值1或者ON表示开启;0或者OFF表示关闭。如果是ON已打开状态,则依次开始执行存储过程、定时器。
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 计划任务,有两种设定计划任务的方式:
在两种计划任务中,时间戳可以是任意的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 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