MySQL事件调度器是数据库内建的、由独立线程驱动的时间触发式SQL执行引擎,不依赖OS cron或应用轮询;它基于information_schema.EVENTS表扫描,支持秒级调度、时区可控、元数据持久化且主从同步。

MySQL事件调度器不是“定时任务的另一种叫法”,而是数据库内建的、由独立线程驱动的时间触发式执行引擎——它不依赖操作系统 cron,也不靠应用层轮询,而是 MySQL 自己在内存里掐着表跑任务。
事件调度器本质是啥?和 cron 有啥区别?
它是一个常驻的后台线程(event_scheduler),启动后会持续扫描 information_schema.EVENTS 表,比对每个事件的 STARTS、ENDS 和当前时间,决定是否触发执行。关键差异:
-
cron是 OS 层进程,执行的是 shell 命令;event_scheduler是 MySQL 内部线程,只执行 SQL(含存储过程、BEGIN...END 块) -
cron最小粒度是分钟;event_scheduler支持秒级甚至亚秒级(如EVERY 5 SECOND),且时间计算基于 MySQL 服务时区(@@time_zone),不是系统时区 - 事件定义存在数据库元数据里(
mysql.event表),主从复制时默认同步;而 cron 配置分散在各服务器文件系统,容易漏配或不一致
怎么确认它真在干活?别被“ON”骗了
很多人执行了 SET GLOBAL event_scheduler = ON 就以为万事大吉,但实际可能根本没生效。必须交叉验证三处:
-
SELECT @@event_scheduler;→ 返回ON(会话级变量,只反映当前连接视角) -
SHOW VARIABLES LIKE 'event_scheduler';→ 返回ON(全局变量,更权威) -
SHOW PROCESSLIST;→ 必须看到一行User: event_scheduler且Command: Daemon(这是线程真正跑起来的铁证)
如果第三条没看到,说明 MySQL 启动时没加载调度器(比如配置文件漏写 event_scheduler=ON,或启用了 --skip-grant-tables 等禁用插件的参数)。
创建每天凌晨 1 点执行的事件,为什么总不准时?
常见错误是直接写 STARTS '2025-12-29 01:00:00' —— 这个时间一过,事件就永远不会触发(除非手动 ALTER EVENT ... ENABLE)。正确做法是让起始时间动态计算:
CREATE EVENT daily_cleanup ON SCHEDULE EVERY 1 DAY STARTS CURRENT_DATE + INTERVAL 1 DAY + INTERVAL 1 HOUR DO DELETE FROM logs WHERE created_at < NOW() - INTERVAL 7 DAY;
解释:CURRENT_DATE + INTERVAL 1 DAY 确保从“明天”开始,+ INTERVAL 1 HOUR 锁定凌晨 1 点;EVERY 1 DAY 保证后续每天自动延续。另外注意:
- 如果服务器时间跳变(如 NTP 校正),事件可能跳过或重复一次,MySQL 不做补偿
-
STARTS和ENDS时间戳必须是 **datetime 类型字面量或表达式**,不能是函数调用(如NOW())直接写在STARTS后,会报语法错 - 事件执行期间若发生锁等待、超时或事务回滚,该次执行即失败,不会重试(无幂等保障)
事件执行失败,去哪儿找日志?
MySQL 默认不记录事件执行日志,出错了只能干瞪眼。最实用的补救方式是:在事件体里加异常捕获 + 日志表写入:
CREATE TABLE IF NOT EXISTS event_log (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
event_name VARCHAR(64),
exec_time DATETIME DEFAULT CURRENT_TIMESTAMP,
status ENUM('success', 'error') DEFAULT 'success',
message TEXT
);
DELIMITER $$
CREATE EVENT daily_report
ON SCHEDULE EVERY 1 DAY STARTS CURRENT_DATE + INTERVAL 1 DAY
DO
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
INSERT INTO event_log (event_name, status, message)
VALUES ('daily_report', 'error', CONCAT('ERROR ', MYSQL_ERRNO(), ': ', MESSAGE_TEXT));
END;
INSERT INTO report_summary SELECT DATE(NOW()), COUNT(*) FROM users;
INSERT INTO event_log (event_name, status) VALUES ('daily_report', 'success');
END$$
DELIMITER ;
这是唯一能快速定位“事件到底有没有跑、卡在哪一步”的方法。别指望 error_log 或 general log——它们不记录事件内部 SQL 的成败。
事件调度器真正难的不是语法,而是它把“时间”这个外部变量塞进了数据库事务模型里:你得同时考虑时区、主从延迟、锁竞争、错误静默这四重干扰。写完一个事件,先手动 ALTER EVENT ... ENABLE 触发一次,再盯 5 分钟 event_log 表,比反复改 STARTS 时间靠谱得多。










