Always Benny&Viva
Copyright © 2006 by MySQL AB
“MySQL 5.1 新特性” 系列
目录
目 录.........................................................................................................................................1
设定事件并运行......................................................................................................................1
CREATE EVENT 的语法..........................................................................................................2
CREATE EVENT 例子.............................................................................................................5
SET GLOBAL event_scheduler = 1 ....................................................................................5
ALTER EVENT 修改事件.........................................................................................................6
DROP EVENT /*删除事件*/...................................................................................................7
The EVENT privilege /*Event 的权限设定*/......................................................................7
Context /*上下文环境设定*/...............................................................................................7
Metadata /*元数据*/...........................................................................................................8
如何获悉事件发生与否?....................................................................................................10
巧妙的制定时间计划(Schedules).................................................................................12
如果发生了如下情况……....................................................................................................13
其他DBMS的情况................................................................................................................14
用例: 替代(延时更新)“INSERT DELAYED” .................................................................15
用例: 实例化视图.................................................................................................................15
用例: Overnight ...................................................................................................................16
远 景.......................................................................................................................................16
Andrey Hristov 先生在完成他的硕士论文的时候为MySQL 5.1 增加了“事件”
的特性,作为开发者,我们都很喜欢这个特性。而现在Andrey Hristoy 也已经
来到MySQL工作了,令人振奋,不是吗?MySQL 5.1 也越来越稳定了,版本号
也由“Beta”变成了“Alpha”,因此我们可以尝试使用这个“事件”特性来进行一
些真正的工作了。
设定事件并运行
我们从MySQL downloads page 上下载了MySQL 5.1 Beta版,安装到XP
上,启动MySQL服务,用root用户打开MySQL客户端:
mysql> use test;
Database changed
mysql> create table test.t (s1 timestamp);
Query OK, 0 rows affected (0.11 sec)
mysql> create event e on schedule every 1 second do
insert into test.t values (current_timestamp);
Query OK, 1 row affected (0.00 sec)
Always Benny&Viva
mysql> set global event_scheduler = 1;
Query OK, 0 rows affected (0.00 sec)
/* 3-second delay */
mysql> select * from test.t;
+---------------------+
| s1 |
+---------------------+
| 2006-04-05 15:44:26 |
| 2006-04-05 15:44:27 |
| 2006-04-05 15:44:28 |
+---------------------+
上面是MySQL “事件”特性的“Hello World”例子,给大家演示的是每隔一秒
钟向行内插入一条时间的记录。
CREATE EVENT 的语法
CREATE EVENT
[IF NOT EXISTS] /* 标注 1 */
event_name /* 标注 2 */
ON SCHEDULE schedule /* 标注3 */
[ON COMPLETION [NOT] PRESERVE] /* 标注4 */
[ENABLE | DISABLE] /* 标注5 */
[COMMENT 'comment'] /* 标注6 */
DO sql_statement /* 标注7 */
标注 1: [IF NOT EXISTS]
你可以在其他的CREATE 语法钟找到类似的语法,这个句子的意思是:如果这
个对象已经存在那么就不管它,如果没有就创建一个。
标注 2: event_name /*事件名称*/
名称最大长度可以是64个字节,可以通过单引号限定(如果你运行在ANSI模
式下是双引号)。名字必须是当前Schema(Database)中唯一的,同一个
数据库或Schema中不能有同名的事件,这是必须的,现在的MySQL Manual
中关于这个的定义可能有错。
提示: 为了避免命名规范带来的不便,最好让事件名称具有描述整个事件的能
力。例如`E1`这个名字就没有清晰描述事件的能力,而`MONTH_END`这个名字
Always Benny&Viva
仅当是周期(月末)运行的时候才具有描述性,但如果你需要每天运行这个事件
就没有意义了。
用例:
MONTH_END
`Background Check`
标注 3: ON SCHEDULE 计划任务
有两种设定计划任务的方式:
AT 时戳
/* "单次的计划任务" */
EVERY 时间(单位)的数量 时间单位 [STARTS 时戳] [ENDS时戳]
/* "重复的计划任务" */
在两种计划任务中,时戳可以是任意的TIMESTAMP 和DATETIME 数据类型,要
求提供的是将来的时间(大于CURRENT_TIMESTAMP),而且小于Unix时间的
最后时间(等于或小于'2037-12-31 23:59:59')。
用例:
TIMESTAMP '2020-01-01 00:00:00' - INTERVAL '3' DAY
CURRENT_TIMESTAMP
timestampadd(YEAR,1,'2020-01-01 00:00:00')
20070707121212
'2007-01-01 23:59:59'
在重复的计划任务中,时间(单位)的数量可以是任意非空(Not Null)的整数
式,时间单位是关键词:YEAR,MONTH,DAY,HOUR,MINUTE 或者SECOND。
提示: 其他的时间单位也是合法的如:QUARTER, WEEK, YEAR_MONTH,
DAY_HOUR, DAY_MINUTE, DAY_SECOND, HOUR_MINUTE,
HOUR_SECOND, MINUTE_SECOND,但他们是非标准不实用也不需要的。
实际上单次的计划任务只是重复的计划任务的一个即时停止的特殊例子。下面这
两个语句虽然在技术上不一样,但实现的效果是一样的:
CREATE EVENT Transient
ON SCHEDULE AT NOW()
DO SET @a=@a;
CREATE EVENT Recurring
ON SCHEDULE EVERY 1 YEAR STARTS NOW() ENDS NOW()
Always Benny&Viva
DO SET @a=@a;
标注 4: [ON COMPLETION [NOT] PRESERVE]
这里COMPLETION的意思是"当这个事件不会再发生的时候",即当单次计划任
务执行完毕后或当重复性的计划任务执行到了ENDS阶段。而声明PRESERVE
的作用是使事件在执行完毕后不会被Drop掉。
提示: 基本不需要试用这个语句。
标注 5: [ENABLE | DISABLE]
在这里设定事件的状态,可选参数为Enable和Disable,如果是Enable 的话,
那么系统就会尝试执行这个事件。有些特殊的时候你可能需要关闭事件特性,你
可以试用下面命令关闭或开启事件:"ALTER EVENT ... ENABLE" or "ALTER
EVENT ... DISABLE".
标注 6: [COMMENT 'comment']
注释会出现在元数据中,它存储在information_schema表的COMMENT列,
最大长度为64个字节。
下面添加注释的方式是不对的:
CREATE EVENT Wrong ON SCHEDULE EVERY 1 DAY
DO /* Daily report for lower management */ CALL report157();
因为行内部的注释会被MySQL的解析器分离,不会被用户看见。所以我们要用
下面的方式来添加注释:
CREATE EVENT Right ON SCHEDULE EVERY 1 DAY
COMMENT 'Daily report for lower management'
DO CALL report157();
提示: 当然最好是事件名字自身就能说明用途,那么就不需要注释了。
标注 7: DO sql_statement /*SQL 语句*/
每当事件被执行的时候,系统就会执行这个部分的SQL语句。
这里的SQL语句可以是复合语句,例如:
BEGIN DROP TABLE test.t1;
DROP TABLE test.t2;
END
如果你在客户端使用复合语句,记得要使用分隔符。
Always Benny&Viva
这部分中使用的SQL语句是有限制的,但对它的限制跟函数Function和触发器
Trigger 中对SQL语句的限制是一样的,如果你在函数Function 和触发器
Trigger 中不能使用某些SQL,同样的在事件中也不能使用。
明确的来说有下面几个:
LOCK TABLES
UNLOCK TABLES
CREATE EVENT (bug#18896)
ALTER EVENT (bug#18896)
LOAD DATA
CREATE EVENT 是不可以预处理的。
CREATE EVENT在存储过程CREATE PROCEDURE中使用是合法的。
CREATE EVENT不能在嵌入式embedded MySQL服务器库中使用。
CREATE EVENT 例子
CREATE EVENT MONITOR_PROCESSLIST
ON SCHEDULE EVERY '1' SECOND
DO INSERT INTO db1.process_counter
SELECT CURRENT_TIMESTAMP,COUNT(*)
FROM INFORMATION_SCHEMA.PROCESSLIST;
- 这个事件会每隔一秒检查“多少作业任务在运行”并将结果和时间存入表中,结
果是每天向process_counter插入24 * 60 * 60 = 86,400条新记录。
CREATE EVENT DROP_TEST_T
AT TIMESTAMP(CURRENT_DATE,'23:59:59')
DO DROP TABLE test.t;
- 事件仅会执行一次,在当天午夜前一分钟执行删除test数据库的表t的动作。
SET GLOBAL event_scheduler = 1
"event scheduler"/*事件计划*/ 是一个监控存在事件并决断是否执行的进程
(操作系统中的一个独立进程),因而它的执行逻辑如下:
For (已建立事件each event that has been created)
If (事件的状态非DISABLE)
And (当前时间在ENDS时间之前)
And (当前时间在STARTS时间之后)
And (在上次执行后经过的时间)
And (没有被执行)
Always Benny&Viva
Then:
建立一个新的线程
传递事件的SQL语句给新的线程
(该线程在执行完毕后会自动关闭)
由于每个事件都有自己的线程,所以原则上事件的并行的。当你计划在同一时间
执行多个事件的时候,它们会并行进行,即使之间可能有一到两秒的间隔。
这个语句"SET GLOBAL event_scheduler = 1"或是"SET GLOBAL
event_scheduler = ON"将启动事件计划进程,如果设置为0 或者OFF将关闭
事件进程。
这里记住,当你设定事件计划为0 或OFF,即关闭事件计划进程的时候,不会
有新的事件执行,但现有的正在运行的事件会执行到完毕。当然在执行SET
GLOBAL 语句的时候,你需要超级用户的权限。
你也可以在命令行启动mysql 服务器的时候执行
“mysqld...--event-scheduler=1”语句来设定事件计划的状态。
ALTER EVENT 修改事件
ALTER EVENT
event_name/*事件名称*/
ON SCHEDULE schedule
[RENAME TO new_event_name]
[ON COMPLETION [NOT] PRESERVE]
[ENABLE | DISABLE]
[COMMENT 'comment']
DO sql_statement
;
ALTER EVENT语句是相当简单的,只要你会使用CREATE EVENT语句,因为它
们语法基本上是一样的。
你可以通过ALTER EVENT修改事件的任意属性,下面举个简单例子:
ALTER EVENT event_name DISABLE
or
ALTER EVENT event_name ENABLE
唯一新的语句就是RENAME,这里没有“RENAME EVENT”语句,要改变事件的
名称只有下面的方法::
ALTER EVENT event_name_1 RENAME TO event_name_2;
Always Benny&Viva
这个新的事件名称也许会在不同的数据库中。
DROP EVENT /*删除事件*/
DROP EVENT
[IF EXISTS]
event_name
;
DROP EVENT与"DROP object"语句的工作方式一样,但当一个事件正在运行
中时,删除不会导致事件的停止,事件会执行到完毕为止。
使用DROP USER和DROP DATABASE 语句同时会将包含其中的事件删除。
The EVENT privilege /*Event 的权限设定*/
GRANT EVENT ON *.* TO user;
or
GRANT EVENT ON database_name.* TO user;
REVOKE EVENT ON *.* FROM user;
or
REVOKE EVENT ON database_name.* FROM user;
在创建(CREATE),修改(ALTER)或者是删除(DROP)事件Event的时候
都需要EVENT的权限。由于事件的功能强大,因此在某些场合下建立事件的用
户的数量一定不能多。
赋予了EVENT权限并不意味着事件一定会被执行,在执行的时候做适当的权限
设定才能让事件执行。例如:如果JOE 创建了一个向表t中插入数据的事件,
然后MySQL会在每次执行事件的时候检查JOE 是否有插入表t的权限。
补充一点,Event权限的设置保存在mysql.user表和mysql.db表的Event_priv
字段中。
Context /*上下文环境设定*/
用户JOE 也许是这样开始一天的工作的:
mysql --user=JOE
SET SQL_MODE='ANSI';
USE database1;
Always Benny&Viva
这就设置了"context"中的部分信息:current_user='JOE', sql_mode='ANSI',
database()='database1'.如果JOE现在创建事件(event),那么这些设定
(user和sql mode)将会传递到事件中,即事件将会以JOE 的名义执行,执
行依据JOE 的权限。因此如果JOE 做下面的动作:
CREATE EVENT e ON SCHEDULE EVERY 1 SECOND DO
INSERT INTO t VALUES (CURRENT_USER());
表t在哪里呢?Joe认为在数据库database1中,但这并不一定可靠,因为系
统不知道在哪里,事件将会失败。Joe应该这样写:
CREATE EVENT e ON SCHEDULE EVERY 1 SECOND DO
INSERT INTO database1.t VALUES (CURRENT_USER());
这样就没有问题了,事件将会执行到正确的表中。顺带的提一下,即使事件执行
的时候Joe没有登陆,这里的CURRENT_USER()还是JOE。
Metadata /*元数据*/
当你建立了事件(event)后,你有五个方式去查看这些事件: 查询mysql.event
表,通过SHOW EVENTS命令,通过SHOW FULL EVENTS命令,通过查询
information_schema.events表,或SHOW CREATE EVENT。下面举个例子:
mysql> CREATE EVENT
-> Event_For_Metadata
-> ON SCHEDULE
-> EVERY 1 DAY
-> STARTS CURRENT_TIMESTAMP
-> ENDS CURRENT_TIMESTAMP + INTERVAL '5' DAY
-> ON COMPLETION NOT PRESERVE
-> DISABLE
-> COMMENT 'Specimen Event Only'
-> DO
-> DROP TABLE test.t;
Query OK, 1 row affected (0.00 sec)
mysql> show events\G
*************************** 1. row ***************************
Db: db1
Name: peter's_event
Definer: root@localhost
Type: RECURRING
Execute at: NULL
Interval value: 1
Always Benny&Viva
Interval field: SECOND
Starts: 2006-04-06 18:09:50
Ends: NULL
Status: ENABLED
1 row in set (0.00 sec)
mysql> SELECT * FROM mysql.event where name =
'Event_For_Metadata'\G
*************************** 1. row ***************************
db: db1
name: Event_For_Metadata
body: DROP TABLE test.t
definer: root@localhost
execute_at: NULL
interval_value: 1
interval_field: DAY
created: 2006-04-06 11:17:31
modified: 2006-04-06 11:17:31
last_executed: NULL
starts: 2006-04-06 17:17:31
ends: 2006-04-11 17:17:31
status: DISABLED
on_completion: DROP
sql_mode:
comment: Specimen Event Only
1 row in set (0.04 sec)
mysql> SELECT * FROM information_schema.events where event_name
= 'Event_For_Metadata'\G
*************************** 1. row ***************************
EVENT_CATALOG: NULL
EVENT_SCHEMA: db1
EVENT_NAME: Event_For_Metadata
DEFINER: root@localhost
EVENT_BODY: DROP TABLE test.t
EVENT_TYPE: RECURRING
EXECUTE_AT: NULL
INTERVAL_VALUE: 1
INTERVAL_FIELD: DAY
SQL_MODE:
STARTS: 2006-04-06 17:17:31
ENDS: 2006-04-11 17:17:31
STATUS: DISABLED
ON_COMPLETION: NOT PRESERVE
Always Benny&Viva
CREATED: 2006-04-06 11:17:31
LAST_ALTERED: 2006-04-06 11:17:31
LAST_EXECUTED: NULL
EVENT_COMMENT: Specimen Event Only
1 row in set (0.01 sec)
mysql> show create event Event_For_Metadata\G
*************************** 1. row ***************************
Event: Event_For_Metadata
sql_mode:
Create Event: CREATE EVENT `Event_For_Metadata` ON SCHEDULE
EVERY 1 DAY ON
COMPLETION NOT PRESERVE DISABLE COMMENT 'Specimen Event Only'
DO DROP TABLE test.t
1 row in set (0.00 sec)
表mysql.event和information_schema.events存储的是同样的信息,但需
要的权限却完全不一样。查询mysql.event表需要对mysql.event表SELECT
的权限,而查询information_schema.events表不需要任何额外的权限,任何
用户都可以查询,但如果你没有对information_schema.events表上对象查询
的权限,在那栏你看到的会是NULLs。
事实上元数据的权限设定问题现在还没有定,MySQL手册是这样解释的:
“在MySQL 5.1.6中,你不可以在INFORMATION_SCHEMA.EVENTS表中查看到
其他用户的事件(events),也就是说任何对这个表的查询在Where从句中都
默认添加了下面条件DEFINER = CURRENT_USER()。”
"查看information_schema.events表数据的权限要求是:用户必须是定义者,
这个设置就太严格了,如果定义的用户被删除了怎么办。
-- Bug#16992
提示: 使用对INFORMATION_SCHEMA表的查询是对元数据查询的标准方法,
我们推荐这样用,因为事件不是标准的SQL对象,但它是
INFORMATION_SCHEMA架构中的。
如何获悉事件发生与否?
事件运行发生了错误或者没有在正确的事件运行,用户是很难知道的。因为事件
没有自身的控制台,即使有,看的人也不会多。但我们还是需要一些方法去监控
事件的运行的,下面就是一些可以用来检查事件运行状态的方法:
Always Benny&Viva
1. (这个方法仅能用于某些非Windows操作系统,是一个调试阶段的功能,最
终是会移除的。),如果你是使用'mysqld'启动MySQL服务器,你可以看到mysqld
控制台给出的一下事件运行的信息:
060406 12:02:59 [Note] SCHEDULER: Started thread 4162
060406 12:02:59 [Note] SCHEDULER: Executing event db1.e_count of
root@localhost
[EXPR:1]
060406 12:02:59 [Warning] SCHEDULER:
[`root`@`localhost`][`db1`.`e_count`]
Data truncated for column 's1' at row -1515870811]
060406 12:02:59 [Note] SCHEDULER: Executed event db1.e_count of
root@localhost
[EXPR:1]. RetCode=0
And here is a typical mysqld display that occurs just after one says 'SET
GLOBAL event_scheduler = OFF':
060406 12:03:01 [Note] SCHEDULER: Emptying the queue.
060406 12:03:01 [Note] SCHEDULER: Stopped.
2. (在写这篇文章的时候此功能尚未实现,但作者对它的期望很高。)使用
--log=log_file_name/*Log 文件名*/命令行的方式启动MySQL服务器。任何
事件的声明和执行的用户都会被记录下来。
3.使用SHOW PROCESSLIST命令。命令会显示正在运行的'event_scheduler'/*
计划事件*/的列表,当这个事件自身有一个进程的时候,它也会显示在列表上面。
4.SELECT LAST_EXECUTED FROM INFORMATION_SCHEMA.EVENTS也可以实
现这个效果。如果LAST_EXECUTED 值为NULL,那就说明事件从没被执行过。
(但不幸的是,在现阶段这个功能还没有启用,详情可以参考bug#17493。)
5.把事件的SQL语句添加到(SQLEXCEPTION)异常处理的 begin和end语
句块中间,那么异常处理就会把符合事件要求的信息记录到数据库中,下面是一
个例子:
mysql> delimiter //
mysql> create table event_failures (event_name varchar(64) character
set utf8,
event_timestamp timestamp);
-> //
Query OK, 0 rows affected (0.08 sec)
mysql> create event event_which_logs_failure on schedule every 1
second do begin
Always Benny&Viva
declare exit handler for sqlexception insert into db1.event_failures
values
('event_which_logs_failure',current_timestamp); insert into
no_such_table values (0); end//
Query OK, 1 row affected (0.00 sec)
6. 使用MySQL的错误记录.
巧妙的制定时间计划(Schedules)
这里有一些制定时间计划的技巧:
· "every Tuesday at 3pm starting next Tuesday" /*下周起每个周二下
午三点执行*/
· "on the last day of each month" /*每月最后一天*/
· "whenever user JOE is connected" /*当用户Joe登陆的时候*/
CREATE EVENT Every_Tuesday_At_3pm
ON SCHEDULE
EVERY 7 DAY
STARTS
CASE WHEN DAYOFWEEK(CURRENT_DATE) > 3
OR (DAYOFWEEK(CURRENT_DATE) = 3 AND
HOUR(CURRENT_TIMESTAMP) >= 15)
THEN
TIMESTAMP(ADDDATE(CURRENT_DATE,(7+3)-DAYOFWEEK(CURRENT_DAT
E)),'15:00:00')
ELSE
TIMESTAMP(ADDDATE(CURRENT_DATE,(3)-DAYOFWEEK(CURRENT_DATE))
,'15:00:00') END
DO ...//
CREATE EVENT End_Of_Month
ON SCHEDULE EVERY 1 DAY
DO
IF MONTH(CURRENT_DATE) <> MONTH(CURRENT_DATE+INTERVAL '1'
DAY) THEN
...
END IF;
Always Benny&Viva
CREATE EVENT if_joe_is_on
ON SCHEDULE EVERY 1 SECOND
DO
IF (SELECT COUNT(*)
FROM information_schema.processlist
WHERE user='joe') > 0
THEN ...; END IF//
还有一个特别的时间计划就是“EVERY n MONTH”/*每隔n个月*/。设想今天是
8 月31 日,然后你设定是“CREATE EVENT ... EVERY 1 MONTH .../*每隔一个月
*/”。那8 月31 日加一个月是什么时候呢?答案就是:9 月30 日。那9 月30
日加一个月呢?答案是10月30日。不过如果是8 月31 日加两个月,答案却是
10 月31日。那个是你所需要的呢?呵呵,看看Bug#16397 上的记录吧。
如果发生了如下情况……
事件的特性使用起来是特别简单的,但某些时候却可能会有特殊情况发生,让你
措手不及。下面就是一些这样的情况:
如果重复执行的事件不见了?
设想一个每隔一分钟重复执行的事件,下一次执行时间是13:00。但在12:59的
时候某个人执行了"SET GLOBAL event_scheduler = OFF"的语句,直到13:01
才把event_scheduler设为ON;或者是服务器停机维护和一些用户执行了极
其占用资源的查询导致事件调度程序不能进行。在以上情况下,事件将会永远失
踪。事件调度程序不会在13:01的时候说 "快,我错过了13:00所以我要现在完
成它"。事实上它就像公共汽车,如果错过了就得搭下一辆。
如果两个事件要同时执行?
首先执行"CREATE EVENT `Conflict_1` ON SCHEDULE AT '2006-12-13
14:15:16' ..."。然后执行"CREATE EVENT `Conflict_2` ON SCHEDULE AT
`2006-12-13 14:15:16' ..."。我们知道事件调度程序会尝试同时执行这两个事件,
以不同的线程并行运作。但那个会是第一个?我们不知道。不要希望`Conflict_1`
会在队列中先运行,不会的,因为这里没有任何优先级别的区分。
夏令时会带来什么问题?
一般来说我们希望系统的时钟会规则运行,但在夏令时里面秋天时钟会慢一个小
时,而春天会快一个小时。这个规则会给你的凌晨1 点到3 点之间运行的事件
带来混乱,所以要避开这些时间段。在写这篇文章的时候,MySQL使用的时间
是UTC,因此没有对夏令时进行调整时间,但这却比解决问题带来更多麻烦,
具体可以看(bug#16420)。
Always Benny&Viva
如果事件已经在运行中?
假设事件X 每秒钟执行,但X的执行速度比较慢,很多时候执行时间会超过一
秒。事件调度程序会检测到这些情况,它会保持X 在执行队列中,然后一秒后
再尝试。但我发现,即使在系统的负担很小,而且X 事件的作业量也不大的情
况下,每天还是会有一些X 事件被调度程序遗失。
如果事件在运行的时候对其进行删除动作?
实现这个例子的最好方法是"CREATE EVENT X ON SCHEDULE EVERY 1
SECOND DO DROP EVENT X",这里的Drop 动作不会立刻终止事件,事件将
会继续,但不会再次运行。而另一方面,如果你使用"delete from mysql.events"
方式删除也会成功,但事件会继续并再次执行,因为事件调度程序并不会在队列
中删除这个事件。
如果终止事件调度程序?
使用SHOW PROCESSLIST, 或者SELECT ... FROM
INFORMATION_SCHEMA.PROCESSLIST,你可以看到'event_scheduler'任务,
或者是实际上在运行的事件进程。如果你有足够权限,你可以杀死这些进程。我
们认为杀死正在运行的事件进程是错误的,虽然大部分情况下影响并不大。事件
事件调度程序一旦被杀死就不会重新启动。
其他DBMS 的情况
事件并非是SQL:2003 的标准,因此从其他关系数据库中移植过来的时候,你必
须做一些修改。
Sybase Adaptive Server IQ 参考手册中提到了如何定时执行一些操作的方
法:
CREATE EVENT IncrementalBackup
SCHEDULE
START TIME '1:00AM' EVERY 24 HOURS
HANDLER
BEGIN
BACKUP DATABASE INCREMENTAL
TO 'backups/daily.incr'
END
source » /*源文件*/
SQLBase's 的CREATE EVENT的语法也跟我们不一样:
Always Benny&Viva
CREATE EVENT rec_stcom
RAISE AT 12:00:00 AM EVERY 1 DAYS
(EXECUTE RECOMPILE ( SYSADM , % , _ ,0,0,0));
source » /*源文件*/
其他的关系数据库也有创建"event"对象的方法,例如DB2 有CREATE EVENT
MONITOR,Ingres数据库有CREATE DBEVENT,同时SQL Server 2005有
CREATE EVENT NOTIFICATION方法。但这些数据库的事件是通过状况或参量
改变而启动的,例如一个对象被删除,而MySQL则是完全严格的依赖时间或时
戳来启动的。
用例: 替代(延时更新)“INSERT DELAYED”
MySQL有一个语句,能在不同的线程上执行插入操作。但我们不需要使用
"INSERT DELAYED",因为事件就是在不同的线程上执行的,所以我们可以利用
线程来实现延时更新:
CREATE EVENT Update_Delayed
ON SCHEDULE AT CURRENT_TIMESTAMP
DO UPDATE database1.table1 SET column1=value1;
这样做并不理想,事件的创建人并不建议这样做,因为在数据库中已经有很多事
件进程了,事件的队列会变得很缓慢。
用例: 实例化视图
设想一个很大的表,在上面做分组查询需要很长的时间,很多用户可以共享一些
分组后的查询结果,他们并不在意这些数据是几分钟前的。
这里我们有一个用法——“实例化视图”来解决这个问题,通过定时更新一些很
难生成的视图。MySQL虽然不直接支持实例化视图,但我们可以通过事件实现:
CREATE TEMPORARY TABLE Mat_View
AS SELECT Category,COUNT(*) FROM Main_Table
GROUP BY Category;
DELIMITER //
CREATE EVENT Mat_View_Refresher
ON SCHEDULE EVERY 10 MINUTE
DO
BEGIN
Always Benny&Viva
DELETE FROM Mat_View;
INSERT INTO Mat_View
SELECT Category,COUNT(*) FROM Main_Table
GROUP BY Category;
END//
不幸的是在事件中我们不能使用LOCK TABLE和UNLOCK TABLE,因此
Mat_View的用户会有短暂的找不到表的情况,但只要重试就好了。
我并不是第一个有这个想法的人,在书的后面我会讲讲这个。
用例: Overnight
每天晚上半夜的时候,我们可以清楚测试数据库中的表格,既然“destoryer/*
清除者*/”是事件之外有用的,我们可以将它做成一个存储过程/*需要存储过程
的资料,可以来我的博客和信箱索取*/,以便日后随时可以调用。
mysql> CREATE PROCEDURE destroyer ()
-> BEGIN
-> DECLARE v VARCHAR(64) CHARACTER SET UTF8;
-> DECLARE c CURSOR FOR
-> SELECT table_name
-> FROM information_schema.tables
-> WHERE table_schema = 'test';
-> DECLARE EXIT HANDLER FOR NOT FOUND BEGIN END;
-> OPEN c;
-> WHILE TRUE DO
-> FETCH c INTO v;
-> SET @v = CONCAT('DROP TABLE test.',v);
-> PREPARE stmt1 FROM @v;
-> EXECUTE stmt1;
-> END WHILE;
-> END//
Query OK, 0 rows affected (0.07 sec)
mysql> CREATE EVENT event_calling_destroyer
-> ON SCHEDULE EVERY 1 DAY
-> STARTS TIMESTAMP(CURRENT_DATE+1,'00:00:01')
-> DO CALL database1.destroyer()//
Query OK, 1 row affected (0.06 sec)
Copyright © 2006 by MySQL AB
“MySQL 5.1 新特性” 系列
目录
目 录.........................................................................................................................................1
设定事件并运行......................................................................................................................1
CREATE EVENT 的语法..........................................................................................................2
CREATE EVENT 例子.............................................................................................................5
SET GLOBAL event_scheduler = 1 ....................................................................................5
ALTER EVENT 修改事件.........................................................................................................6
DROP EVENT /*删除事件*/...................................................................................................7
The EVENT privilege /*Event 的权限设定*/......................................................................7
Context /*上下文环境设定*/...............................................................................................7
Metadata /*元数据*/...........................................................................................................8
如何获悉事件发生与否?....................................................................................................10
巧妙的制定时间计划(Schedules).................................................................................12
如果发生了如下情况……....................................................................................................13
其他DBMS的情况................................................................................................................14
用例: 替代(延时更新)“INSERT DELAYED” .................................................................15
用例: 实例化视图.................................................................................................................15
用例: Overnight ...................................................................................................................16
远 景.......................................................................................................................................16
Andrey Hristov 先生在完成他的硕士论文的时候为MySQL 5.1 增加了“事件”
的特性,作为开发者,我们都很喜欢这个特性。而现在Andrey Hristoy 也已经
来到MySQL工作了,令人振奋,不是吗?MySQL 5.1 也越来越稳定了,版本号
也由“Beta”变成了“Alpha”,因此我们可以尝试使用这个“事件”特性来进行一
些真正的工作了。
设定事件并运行
我们从MySQL downloads page 上下载了MySQL 5.1 Beta版,安装到XP
上,启动MySQL服务,用root用户打开MySQL客户端:
mysql> use test;
Database changed
mysql> create table test.t (s1 timestamp);
Query OK, 0 rows affected (0.11 sec)
mysql> create event e on schedule every 1 second do
insert into test.t values (current_timestamp);
Query OK, 1 row affected (0.00 sec)
Always Benny&Viva
mysql> set global event_scheduler = 1;
Query OK, 0 rows affected (0.00 sec)
/* 3-second delay */
mysql> select * from test.t;
+---------------------+
| s1 |
+---------------------+
| 2006-04-05 15:44:26 |
| 2006-04-05 15:44:27 |
| 2006-04-05 15:44:28 |
+---------------------+
上面是MySQL “事件”特性的“Hello World”例子,给大家演示的是每隔一秒
钟向行内插入一条时间的记录。
CREATE EVENT 的语法
CREATE EVENT
[IF NOT EXISTS] /* 标注 1 */
event_name /* 标注 2 */
ON SCHEDULE schedule /* 标注3 */
[ON COMPLETION [NOT] PRESERVE] /* 标注4 */
[ENABLE | DISABLE] /* 标注5 */
[COMMENT 'comment'] /* 标注6 */
DO sql_statement /* 标注7 */
标注 1: [IF NOT EXISTS]
你可以在其他的CREATE 语法钟找到类似的语法,这个句子的意思是:如果这
个对象已经存在那么就不管它,如果没有就创建一个。
标注 2: event_name /*事件名称*/
名称最大长度可以是64个字节,可以通过单引号限定(如果你运行在ANSI模
式下是双引号)。名字必须是当前Schema(Database)中唯一的,同一个
数据库或Schema中不能有同名的事件,这是必须的,现在的MySQL Manual
中关于这个的定义可能有错。
提示: 为了避免命名规范带来的不便,最好让事件名称具有描述整个事件的能
力。例如`E1`这个名字就没有清晰描述事件的能力,而`MONTH_END`这个名字
Always Benny&Viva
仅当是周期(月末)运行的时候才具有描述性,但如果你需要每天运行这个事件
就没有意义了。
用例:
MONTH_END
`Background Check`
标注 3: ON SCHEDULE 计划任务
有两种设定计划任务的方式:
AT 时戳
/* "单次的计划任务" */
EVERY 时间(单位)的数量 时间单位 [STARTS 时戳] [ENDS时戳]
/* "重复的计划任务" */
在两种计划任务中,时戳可以是任意的TIMESTAMP 和DATETIME 数据类型,要
求提供的是将来的时间(大于CURRENT_TIMESTAMP),而且小于Unix时间的
最后时间(等于或小于'2037-12-31 23:59:59')。
用例:
TIMESTAMP '2020-01-01 00:00:00' - INTERVAL '3' DAY
CURRENT_TIMESTAMP
timestampadd(YEAR,1,'2020-01-01 00:00:00')
20070707121212
'2007-01-01 23:59:59'
在重复的计划任务中,时间(单位)的数量可以是任意非空(Not Null)的整数
式,时间单位是关键词:YEAR,MONTH,DAY,HOUR,MINUTE 或者SECOND。
提示: 其他的时间单位也是合法的如:QUARTER, WEEK, YEAR_MONTH,
DAY_HOUR, DAY_MINUTE, DAY_SECOND, HOUR_MINUTE,
HOUR_SECOND, MINUTE_SECOND,但他们是非标准不实用也不需要的。
实际上单次的计划任务只是重复的计划任务的一个即时停止的特殊例子。下面这
两个语句虽然在技术上不一样,但实现的效果是一样的:
CREATE EVENT Transient
ON SCHEDULE AT NOW()
DO SET @a=@a;
CREATE EVENT Recurring
ON SCHEDULE EVERY 1 YEAR STARTS NOW() ENDS NOW()
Always Benny&Viva
DO SET @a=@a;
标注 4: [ON COMPLETION [NOT] PRESERVE]
这里COMPLETION的意思是"当这个事件不会再发生的时候",即当单次计划任
务执行完毕后或当重复性的计划任务执行到了ENDS阶段。而声明PRESERVE
的作用是使事件在执行完毕后不会被Drop掉。
提示: 基本不需要试用这个语句。
标注 5: [ENABLE | DISABLE]
在这里设定事件的状态,可选参数为Enable和Disable,如果是Enable 的话,
那么系统就会尝试执行这个事件。有些特殊的时候你可能需要关闭事件特性,你
可以试用下面命令关闭或开启事件:"ALTER EVENT ... ENABLE" or "ALTER
EVENT ... DISABLE".
标注 6: [COMMENT 'comment']
注释会出现在元数据中,它存储在information_schema表的COMMENT列,
最大长度为64个字节。
下面添加注释的方式是不对的:
CREATE EVENT Wrong ON SCHEDULE EVERY 1 DAY
DO /* Daily report for lower management */ CALL report157();
因为行内部的注释会被MySQL的解析器分离,不会被用户看见。所以我们要用
下面的方式来添加注释:
CREATE EVENT Right ON SCHEDULE EVERY 1 DAY
COMMENT 'Daily report for lower management'
DO CALL report157();
提示: 当然最好是事件名字自身就能说明用途,那么就不需要注释了。
标注 7: DO sql_statement /*SQL 语句*/
每当事件被执行的时候,系统就会执行这个部分的SQL语句。
这里的SQL语句可以是复合语句,例如:
BEGIN DROP TABLE test.t1;
DROP TABLE test.t2;
END
如果你在客户端使用复合语句,记得要使用分隔符。
Always Benny&Viva
这部分中使用的SQL语句是有限制的,但对它的限制跟函数Function和触发器
Trigger 中对SQL语句的限制是一样的,如果你在函数Function 和触发器
Trigger 中不能使用某些SQL,同样的在事件中也不能使用。
明确的来说有下面几个:
LOCK TABLES
UNLOCK TABLES
CREATE EVENT (bug#18896)
ALTER EVENT (bug#18896)
LOAD DATA
CREATE EVENT 是不可以预处理的。
CREATE EVENT在存储过程CREATE PROCEDURE中使用是合法的。
CREATE EVENT不能在嵌入式embedded MySQL服务器库中使用。
CREATE EVENT 例子
CREATE EVENT MONITOR_PROCESSLIST
ON SCHEDULE EVERY '1' SECOND
DO INSERT INTO db1.process_counter
SELECT CURRENT_TIMESTAMP,COUNT(*)
FROM INFORMATION_SCHEMA.PROCESSLIST;
- 这个事件会每隔一秒检查“多少作业任务在运行”并将结果和时间存入表中,结
果是每天向process_counter插入24 * 60 * 60 = 86,400条新记录。
CREATE EVENT DROP_TEST_T
AT TIMESTAMP(CURRENT_DATE,'23:59:59')
DO DROP TABLE test.t;
- 事件仅会执行一次,在当天午夜前一分钟执行删除test数据库的表t的动作。
SET GLOBAL event_scheduler = 1
"event scheduler"/*事件计划*/ 是一个监控存在事件并决断是否执行的进程
(操作系统中的一个独立进程),因而它的执行逻辑如下:
For (已建立事件each event that has been created)
If (事件的状态非DISABLE)
And (当前时间在ENDS时间之前)
And (当前时间在STARTS时间之后)
And (在上次执行后经过的时间)
And (没有被执行)
Always Benny&Viva
Then:
建立一个新的线程
传递事件的SQL语句给新的线程
(该线程在执行完毕后会自动关闭)
由于每个事件都有自己的线程,所以原则上事件的并行的。当你计划在同一时间
执行多个事件的时候,它们会并行进行,即使之间可能有一到两秒的间隔。
这个语句"SET GLOBAL event_scheduler = 1"或是"SET GLOBAL
event_scheduler = ON"将启动事件计划进程,如果设置为0 或者OFF将关闭
事件进程。
这里记住,当你设定事件计划为0 或OFF,即关闭事件计划进程的时候,不会
有新的事件执行,但现有的正在运行的事件会执行到完毕。当然在执行SET
GLOBAL 语句的时候,你需要超级用户的权限。
你也可以在命令行启动mysql 服务器的时候执行
“mysqld...--event-scheduler=1”语句来设定事件计划的状态。
ALTER EVENT 修改事件
ALTER EVENT
event_name/*事件名称*/
ON SCHEDULE schedule
[RENAME TO new_event_name]
[ON COMPLETION [NOT] PRESERVE]
[ENABLE | DISABLE]
[COMMENT 'comment']
DO sql_statement
;
ALTER EVENT语句是相当简单的,只要你会使用CREATE EVENT语句,因为它
们语法基本上是一样的。
你可以通过ALTER EVENT修改事件的任意属性,下面举个简单例子:
ALTER EVENT event_name DISABLE
or
ALTER EVENT event_name ENABLE
唯一新的语句就是RENAME,这里没有“RENAME EVENT”语句,要改变事件的
名称只有下面的方法::
ALTER EVENT event_name_1 RENAME TO event_name_2;
Always Benny&Viva
这个新的事件名称也许会在不同的数据库中。
DROP EVENT /*删除事件*/
DROP EVENT
[IF EXISTS]
event_name
;
DROP EVENT与"DROP object"语句的工作方式一样,但当一个事件正在运行
中时,删除不会导致事件的停止,事件会执行到完毕为止。
使用DROP USER和DROP DATABASE 语句同时会将包含其中的事件删除。
The EVENT privilege /*Event 的权限设定*/
GRANT EVENT ON *.* TO user;
or
GRANT EVENT ON database_name.* TO user;
REVOKE EVENT ON *.* FROM user;
or
REVOKE EVENT ON database_name.* FROM user;
在创建(CREATE),修改(ALTER)或者是删除(DROP)事件Event的时候
都需要EVENT的权限。由于事件的功能强大,因此在某些场合下建立事件的用
户的数量一定不能多。
赋予了EVENT权限并不意味着事件一定会被执行,在执行的时候做适当的权限
设定才能让事件执行。例如:如果JOE 创建了一个向表t中插入数据的事件,
然后MySQL会在每次执行事件的时候检查JOE 是否有插入表t的权限。
补充一点,Event权限的设置保存在mysql.user表和mysql.db表的Event_priv
字段中。
Context /*上下文环境设定*/
用户JOE 也许是这样开始一天的工作的:
mysql --user=JOE
SET SQL_MODE='ANSI';
USE database1;
Always Benny&Viva
这就设置了"context"中的部分信息:current_user='JOE', sql_mode='ANSI',
database()='database1'.如果JOE现在创建事件(event),那么这些设定
(user和sql mode)将会传递到事件中,即事件将会以JOE 的名义执行,执
行依据JOE 的权限。因此如果JOE 做下面的动作:
CREATE EVENT e ON SCHEDULE EVERY 1 SECOND DO
INSERT INTO t VALUES (CURRENT_USER());
表t在哪里呢?Joe认为在数据库database1中,但这并不一定可靠,因为系
统不知道在哪里,事件将会失败。Joe应该这样写:
CREATE EVENT e ON SCHEDULE EVERY 1 SECOND DO
INSERT INTO database1.t VALUES (CURRENT_USER());
这样就没有问题了,事件将会执行到正确的表中。顺带的提一下,即使事件执行
的时候Joe没有登陆,这里的CURRENT_USER()还是JOE。
Metadata /*元数据*/
当你建立了事件(event)后,你有五个方式去查看这些事件: 查询mysql.event
表,通过SHOW EVENTS命令,通过SHOW FULL EVENTS命令,通过查询
information_schema.events表,或SHOW CREATE EVENT。下面举个例子:
mysql> CREATE EVENT
-> Event_For_Metadata
-> ON SCHEDULE
-> EVERY 1 DAY
-> STARTS CURRENT_TIMESTAMP
-> ENDS CURRENT_TIMESTAMP + INTERVAL '5' DAY
-> ON COMPLETION NOT PRESERVE
-> DISABLE
-> COMMENT 'Specimen Event Only'
-> DO
-> DROP TABLE test.t;
Query OK, 1 row affected (0.00 sec)
mysql> show events\G
*************************** 1. row ***************************
Db: db1
Name: peter's_event
Definer: root@localhost
Type: RECURRING
Execute at: NULL
Interval value: 1
Always Benny&Viva
Interval field: SECOND
Starts: 2006-04-06 18:09:50
Ends: NULL
Status: ENABLED
1 row in set (0.00 sec)
mysql> SELECT * FROM mysql.event where name =
'Event_For_Metadata'\G
*************************** 1. row ***************************
db: db1
name: Event_For_Metadata
body: DROP TABLE test.t
definer: root@localhost
execute_at: NULL
interval_value: 1
interval_field: DAY
created: 2006-04-06 11:17:31
modified: 2006-04-06 11:17:31
last_executed: NULL
starts: 2006-04-06 17:17:31
ends: 2006-04-11 17:17:31
status: DISABLED
on_completion: DROP
sql_mode:
comment: Specimen Event Only
1 row in set (0.04 sec)
mysql> SELECT * FROM information_schema.events where event_name
= 'Event_For_Metadata'\G
*************************** 1. row ***************************
EVENT_CATALOG: NULL
EVENT_SCHEMA: db1
EVENT_NAME: Event_For_Metadata
DEFINER: root@localhost
EVENT_BODY: DROP TABLE test.t
EVENT_TYPE: RECURRING
EXECUTE_AT: NULL
INTERVAL_VALUE: 1
INTERVAL_FIELD: DAY
SQL_MODE:
STARTS: 2006-04-06 17:17:31
ENDS: 2006-04-11 17:17:31
STATUS: DISABLED
ON_COMPLETION: NOT PRESERVE
Always Benny&Viva
CREATED: 2006-04-06 11:17:31
LAST_ALTERED: 2006-04-06 11:17:31
LAST_EXECUTED: NULL
EVENT_COMMENT: Specimen Event Only
1 row in set (0.01 sec)
mysql> show create event Event_For_Metadata\G
*************************** 1. row ***************************
Event: Event_For_Metadata
sql_mode:
Create Event: CREATE EVENT `Event_For_Metadata` ON SCHEDULE
EVERY 1 DAY ON
COMPLETION NOT PRESERVE DISABLE COMMENT 'Specimen Event Only'
DO DROP TABLE test.t
1 row in set (0.00 sec)
表mysql.event和information_schema.events存储的是同样的信息,但需
要的权限却完全不一样。查询mysql.event表需要对mysql.event表SELECT
的权限,而查询information_schema.events表不需要任何额外的权限,任何
用户都可以查询,但如果你没有对information_schema.events表上对象查询
的权限,在那栏你看到的会是NULLs。
事实上元数据的权限设定问题现在还没有定,MySQL手册是这样解释的:
“在MySQL 5.1.6中,你不可以在INFORMATION_SCHEMA.EVENTS表中查看到
其他用户的事件(events),也就是说任何对这个表的查询在Where从句中都
默认添加了下面条件DEFINER = CURRENT_USER()。”
"查看information_schema.events表数据的权限要求是:用户必须是定义者,
这个设置就太严格了,如果定义的用户被删除了怎么办。
-- Bug#16992
提示: 使用对INFORMATION_SCHEMA表的查询是对元数据查询的标准方法,
我们推荐这样用,因为事件不是标准的SQL对象,但它是
INFORMATION_SCHEMA架构中的。
如何获悉事件发生与否?
事件运行发生了错误或者没有在正确的事件运行,用户是很难知道的。因为事件
没有自身的控制台,即使有,看的人也不会多。但我们还是需要一些方法去监控
事件的运行的,下面就是一些可以用来检查事件运行状态的方法:
Always Benny&Viva
1. (这个方法仅能用于某些非Windows操作系统,是一个调试阶段的功能,最
终是会移除的。),如果你是使用'mysqld'启动MySQL服务器,你可以看到mysqld
控制台给出的一下事件运行的信息:
060406 12:02:59 [Note] SCHEDULER: Started thread 4162
060406 12:02:59 [Note] SCHEDULER: Executing event db1.e_count of
root@localhost
[EXPR:1]
060406 12:02:59 [Warning] SCHEDULER:
[`root`@`localhost`][`db1`.`e_count`]
Data truncated for column 's1' at row -1515870811]
060406 12:02:59 [Note] SCHEDULER: Executed event db1.e_count of
root@localhost
[EXPR:1]. RetCode=0
And here is a typical mysqld display that occurs just after one says 'SET
GLOBAL event_scheduler = OFF':
060406 12:03:01 [Note] SCHEDULER: Emptying the queue.
060406 12:03:01 [Note] SCHEDULER: Stopped.
2. (在写这篇文章的时候此功能尚未实现,但作者对它的期望很高。)使用
--log=log_file_name/*Log 文件名*/命令行的方式启动MySQL服务器。任何
事件的声明和执行的用户都会被记录下来。
3.使用SHOW PROCESSLIST命令。命令会显示正在运行的'event_scheduler'/*
计划事件*/的列表,当这个事件自身有一个进程的时候,它也会显示在列表上面。
4.SELECT LAST_EXECUTED FROM INFORMATION_SCHEMA.EVENTS也可以实
现这个效果。如果LAST_EXECUTED 值为NULL,那就说明事件从没被执行过。
(但不幸的是,在现阶段这个功能还没有启用,详情可以参考bug#17493。)
5.把事件的SQL语句添加到(SQLEXCEPTION)异常处理的 begin和end语
句块中间,那么异常处理就会把符合事件要求的信息记录到数据库中,下面是一
个例子:
mysql> delimiter //
mysql> create table event_failures (event_name varchar(64) character
set utf8,
event_timestamp timestamp);
-> //
Query OK, 0 rows affected (0.08 sec)
mysql> create event event_which_logs_failure on schedule every 1
second do begin
Always Benny&Viva
declare exit handler for sqlexception insert into db1.event_failures
values
('event_which_logs_failure',current_timestamp); insert into
no_such_table values (0); end//
Query OK, 1 row affected (0.00 sec)
6. 使用MySQL的错误记录.
巧妙的制定时间计划(Schedules)
这里有一些制定时间计划的技巧:
· "every Tuesday at 3pm starting next Tuesday" /*下周起每个周二下
午三点执行*/
· "on the last day of each month" /*每月最后一天*/
· "whenever user JOE is connected" /*当用户Joe登陆的时候*/
CREATE EVENT Every_Tuesday_At_3pm
ON SCHEDULE
EVERY 7 DAY
STARTS
CASE WHEN DAYOFWEEK(CURRENT_DATE) > 3
OR (DAYOFWEEK(CURRENT_DATE) = 3 AND
HOUR(CURRENT_TIMESTAMP) >= 15)
THEN
TIMESTAMP(ADDDATE(CURRENT_DATE,(7+3)-DAYOFWEEK(CURRENT_DAT
E)),'15:00:00')
ELSE
TIMESTAMP(ADDDATE(CURRENT_DATE,(3)-DAYOFWEEK(CURRENT_DATE))
,'15:00:00') END
DO ...//
CREATE EVENT End_Of_Month
ON SCHEDULE EVERY 1 DAY
DO
IF MONTH(CURRENT_DATE) <> MONTH(CURRENT_DATE+INTERVAL '1'
DAY) THEN
...
END IF;
Always Benny&Viva
CREATE EVENT if_joe_is_on
ON SCHEDULE EVERY 1 SECOND
DO
IF (SELECT COUNT(*)
FROM information_schema.processlist
WHERE user='joe') > 0
THEN ...; END IF//
还有一个特别的时间计划就是“EVERY n MONTH”/*每隔n个月*/。设想今天是
8 月31 日,然后你设定是“CREATE EVENT ... EVERY 1 MONTH .../*每隔一个月
*/”。那8 月31 日加一个月是什么时候呢?答案就是:9 月30 日。那9 月30
日加一个月呢?答案是10月30日。不过如果是8 月31 日加两个月,答案却是
10 月31日。那个是你所需要的呢?呵呵,看看Bug#16397 上的记录吧。
如果发生了如下情况……
事件的特性使用起来是特别简单的,但某些时候却可能会有特殊情况发生,让你
措手不及。下面就是一些这样的情况:
如果重复执行的事件不见了?
设想一个每隔一分钟重复执行的事件,下一次执行时间是13:00。但在12:59的
时候某个人执行了"SET GLOBAL event_scheduler = OFF"的语句,直到13:01
才把event_scheduler设为ON;或者是服务器停机维护和一些用户执行了极
其占用资源的查询导致事件调度程序不能进行。在以上情况下,事件将会永远失
踪。事件调度程序不会在13:01的时候说 "快,我错过了13:00所以我要现在完
成它"。事实上它就像公共汽车,如果错过了就得搭下一辆。
如果两个事件要同时执行?
首先执行"CREATE EVENT `Conflict_1` ON SCHEDULE AT '2006-12-13
14:15:16' ..."。然后执行"CREATE EVENT `Conflict_2` ON SCHEDULE AT
`2006-12-13 14:15:16' ..."。我们知道事件调度程序会尝试同时执行这两个事件,
以不同的线程并行运作。但那个会是第一个?我们不知道。不要希望`Conflict_1`
会在队列中先运行,不会的,因为这里没有任何优先级别的区分。
夏令时会带来什么问题?
一般来说我们希望系统的时钟会规则运行,但在夏令时里面秋天时钟会慢一个小
时,而春天会快一个小时。这个规则会给你的凌晨1 点到3 点之间运行的事件
带来混乱,所以要避开这些时间段。在写这篇文章的时候,MySQL使用的时间
是UTC,因此没有对夏令时进行调整时间,但这却比解决问题带来更多麻烦,
具体可以看(bug#16420)。
Always Benny&Viva
如果事件已经在运行中?
假设事件X 每秒钟执行,但X的执行速度比较慢,很多时候执行时间会超过一
秒。事件调度程序会检测到这些情况,它会保持X 在执行队列中,然后一秒后
再尝试。但我发现,即使在系统的负担很小,而且X 事件的作业量也不大的情
况下,每天还是会有一些X 事件被调度程序遗失。
如果事件在运行的时候对其进行删除动作?
实现这个例子的最好方法是"CREATE EVENT X ON SCHEDULE EVERY 1
SECOND DO DROP EVENT X",这里的Drop 动作不会立刻终止事件,事件将
会继续,但不会再次运行。而另一方面,如果你使用"delete from mysql.events"
方式删除也会成功,但事件会继续并再次执行,因为事件调度程序并不会在队列
中删除这个事件。
如果终止事件调度程序?
使用SHOW PROCESSLIST, 或者SELECT ... FROM
INFORMATION_SCHEMA.PROCESSLIST,你可以看到'event_scheduler'任务,
或者是实际上在运行的事件进程。如果你有足够权限,你可以杀死这些进程。我
们认为杀死正在运行的事件进程是错误的,虽然大部分情况下影响并不大。事件
事件调度程序一旦被杀死就不会重新启动。
其他DBMS 的情况
事件并非是SQL:2003 的标准,因此从其他关系数据库中移植过来的时候,你必
须做一些修改。
Sybase Adaptive Server IQ 参考手册中提到了如何定时执行一些操作的方
法:
CREATE EVENT IncrementalBackup
SCHEDULE
START TIME '1:00AM' EVERY 24 HOURS
HANDLER
BEGIN
BACKUP DATABASE INCREMENTAL
TO 'backups/daily.incr'
END
source » /*源文件*/
SQLBase's 的CREATE EVENT的语法也跟我们不一样:
Always Benny&Viva
CREATE EVENT rec_stcom
RAISE AT 12:00:00 AM EVERY 1 DAYS
(EXECUTE RECOMPILE ( SYSADM , % , _ ,0,0,0));
source » /*源文件*/
其他的关系数据库也有创建"event"对象的方法,例如DB2 有CREATE EVENT
MONITOR,Ingres数据库有CREATE DBEVENT,同时SQL Server 2005有
CREATE EVENT NOTIFICATION方法。但这些数据库的事件是通过状况或参量
改变而启动的,例如一个对象被删除,而MySQL则是完全严格的依赖时间或时
戳来启动的。
用例: 替代(延时更新)“INSERT DELAYED”
MySQL有一个语句,能在不同的线程上执行插入操作。但我们不需要使用
"INSERT DELAYED",因为事件就是在不同的线程上执行的,所以我们可以利用
线程来实现延时更新:
CREATE EVENT Update_Delayed
ON SCHEDULE AT CURRENT_TIMESTAMP
DO UPDATE database1.table1 SET column1=value1;
这样做并不理想,事件的创建人并不建议这样做,因为在数据库中已经有很多事
件进程了,事件的队列会变得很缓慢。
用例: 实例化视图
设想一个很大的表,在上面做分组查询需要很长的时间,很多用户可以共享一些
分组后的查询结果,他们并不在意这些数据是几分钟前的。
这里我们有一个用法——“实例化视图”来解决这个问题,通过定时更新一些很
难生成的视图。MySQL虽然不直接支持实例化视图,但我们可以通过事件实现:
CREATE TEMPORARY TABLE Mat_View
AS SELECT Category,COUNT(*) FROM Main_Table
GROUP BY Category;
DELIMITER //
CREATE EVENT Mat_View_Refresher
ON SCHEDULE EVERY 10 MINUTE
DO
BEGIN
Always Benny&Viva
DELETE FROM Mat_View;
INSERT INTO Mat_View
SELECT Category,COUNT(*) FROM Main_Table
GROUP BY Category;
END//
不幸的是在事件中我们不能使用LOCK TABLE和UNLOCK TABLE,因此
Mat_View的用户会有短暂的找不到表的情况,但只要重试就好了。
我并不是第一个有这个想法的人,在书的后面我会讲讲这个。
用例: Overnight
每天晚上半夜的时候,我们可以清楚测试数据库中的表格,既然“destoryer/*
清除者*/”是事件之外有用的,我们可以将它做成一个存储过程/*需要存储过程
的资料,可以来我的博客和信箱索取*/,以便日后随时可以调用。
mysql> CREATE PROCEDURE destroyer ()
-> BEGIN
-> DECLARE v VARCHAR(64) CHARACTER SET UTF8;
-> DECLARE c CURSOR FOR
-> SELECT table_name
-> FROM information_schema.tables
-> WHERE table_schema = 'test';
-> DECLARE EXIT HANDLER FOR NOT FOUND BEGIN END;
-> OPEN c;
-> WHILE TRUE DO
-> FETCH c INTO v;
-> SET @v = CONCAT('DROP TABLE test.',v);
-> PREPARE stmt1 FROM @v;
-> EXECUTE stmt1;
-> END WHILE;
-> END//
Query OK, 0 rows affected (0.07 sec)
mysql> CREATE EVENT event_calling_destroyer
-> ON SCHEDULE EVERY 1 DAY
-> STARTS TIMESTAMP(CURRENT_DATE+1,'00:00:01')
-> DO CALL database1.destroyer()//
Query OK, 1 row affected (0.06 sec)