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

你可能感兴趣的:(mysql,触发器,存储过程,定时器)