MySQL用事件调度器做定时任务
MySQL 自带事件调度器(Event Scheduler),不依赖 cron 或外部工具,就能在数据库层面跑定时任务。这篇把事件调度器的开启方式、创建语法、常见场景和注意事项都过一遍。
很多人跑定时任务第一反应是 Linux 的 cron,或者在应用层起个定时线程。
但如果任务本身就是数据库操作——比如定期清日志、定时汇总统计数据——直接用 MySQL 自带的事件调度器往往更简单,不用多维护一套外部调度。
事件调度器是什么
MySQL 5.1 开始内置了事件调度器(Event Scheduler)。
它的作用很直白:按照你定义的时间规则,自动执行一段 SQL 语句。
可以理解成数据库层面的 cron,只不过执行的是 SQL,不是 Shell 命令。
先把调度器开起来
事件调度器默认是关闭的,先检查状态:
SHOW VARIABLES LIKE 'event_scheduler';
如果是 OFF,临时开启:
SET GLOBAL event_scheduler = ON;
这个方式重启 MySQL 后会失效。要持久生效,得在 my.cnf(Linux)或 my.ini(Windows)里加上:
[mysqld]
event_scheduler = ON
改完重启 MySQL 服务生效。
创建一个事件
基础语法:
CREATE EVENT event_name
ON SCHEDULE schedule
DO
sql_statement;
schedule 有两种形式:
AT timestamp:在某个固定时间点执行一次EVERY interval:按间隔重复执行
一次性执行
比如 5 分钟后删一批测试数据:
CREATE EVENT clean_test_data
ON SCHEDULE AT NOW() + INTERVAL 5 MINUTE
DO
DELETE FROM mebugs_log WHERE type = 'test';
执行完自动销毁,不用手动清理。
周期性执行
每天凌晨 2 点清理 30 天前的操作日志:
CREATE EVENT daily_clean_log
ON SCHEDULE EVERY 1 DAY
STARTS '2026-01-01 02:00:00'
DO
DELETE FROM mebugs_operate_log
WHERE created_at < NOW() - INTERVAL 30 DAY;
STARTS 指定第一次执行时间,之后每隔 1 天执行一次。
不加 ENDS 就是一直跑,加了 ENDS 会在指定时间后停止。
执行多条语句
DO 后面只能跟一条 SQL。要执行多条,用 BEGIN...END 包起来:
DELIMITER $$
CREATE EVENT monthly_summary
ON SCHEDULE EVERY 1 MONTH
STARTS '2026-01-01 03:00:00'
DO
BEGIN
-- 汇总上月数据
INSERT INTO mebugs_monthly_stat (month, pv, uv, created_at)
SELECT
DATE_FORMAT(NOW() - INTERVAL 1 MONTH, '%Y-%m'),
COUNT(*),
COUNT(DISTINCT user_id),
NOW()
FROM mebugs_access_log
WHERE created_at >= DATE_FORMAT(NOW() - INTERVAL 1 MONTH, '%Y-%m-01')
AND created_at < DATE_FORMAT(NOW(), '%Y-%m-01');
-- 清理三个月前的明细日志
DELETE FROM mebugs_access_log
WHERE created_at < NOW() - INTERVAL 3 MONTH;
END$$
DELIMITER ;
注意要用 DELIMITER 切换语句分隔符,不然 BEGIN...END 里的分号会被 MySQL 客户端提前截断。
管理已有事件
查看当前库所有事件:
SHOW EVENTS;
-- 或者更详细的
SELECT * FROM information_schema.EVENTS WHERE EVENT_SCHEMA = 'site_ol'\G
暂停和恢复:
-- 暂停事件
ALTER EVENT daily_clean_log DISABLE;
-- 恢复事件
ALTER EVENT daily_clean_log ENABLE;
修改执行时间:
ALTER EVENT daily_clean_log
ON SCHEDULE EVERY 1 DAY
STARTS '2026-01-01 03:30:00';
删除事件:
DROP EVENT IF EXISTS daily_clean_log;
几个坑和注意事项
事件调度器是全局开关
event_scheduler 是全局变量,一关就是整个 MySQL 实例上所有库的事件全停。
如果你在共享数据库环境里操作,关之前确认一下别人有没有在跑事件。
事件没有执行日志
这是比较蛋疼的地方——MySQL 默认不记录事件的执行历史,跑了没跑、成功还是失败,你不知道。
如果需要追踪,可以在事件的 DO 逻辑里手动 INSERT 一条执行记录到日志表:
INSERT INTO mebugs_event_log (event_name, executed_at, status)
VALUES ('daily_clean_log', NOW(), 'done');
时区问题
STARTS 里的时间是按 MySQL 服务器的时区解释的。
如果你的应用服务器和数据库服务器时区不一致,要留意,不然定时时间会偏。
用 SELECT NOW(), @@global.time_zone, @@session.time_zone; 确认一下时区再配时间。
主从复制环境下的注意
事件调度器默认只在主库上跑,从库上即使有相同的事件定义,event_scheduler 也是 OFF 的。
主从切换(比如故障切换或主动 Failover)后,新主库上要手动把事件调度器开起来,不然事件会停跑。
适合用事件调度器的场景
我自己会考虑用事件调度器的场景大概这几类:
- 定期清理历史日志、过期数据
- 定时汇总统计数据(日/月报表预聚合)
- 定时更新某个汇总字段(比如更新文章的阅读量均值)
- 一次性的延迟执行(比如 N 分钟后把某条记录标记为超时)
不太适合的场景:需要根据执行结果做分支处理、需要跨库跨服务的协调、对执行精度要求极高(秒级以内)。
这类情况还是老老实实上专业的调度框架(xxl-job、Quartz 这类)比较稳。
小结
MySQL 事件调度器用起来挺轻巧:
- 开启
event_scheduler = ON CREATE EVENT定义时间规则和要执行的 SQL- 多条语句用
BEGIN...END包裹 - 用
ALTER EVENT管理事件状态
适合纯数据库层面的定时维护任务,不需要额外维护外部调度服务。
需要执行历史的话,记得自己建一张日志表,手动记录每次执行情况。
