事件调度器
导学
MySQL 事件调度器(Event Scheduler)是数据库内置的"定时任务"系统。无需借助外部 Cron,你就可以让 MySQL 在指定时间或按周期自动执行 SQL。
定义
事件调度器(Event Scheduler):MySQL 的后台线程,用于在预定的时间点或按预定周期自动执行指定的事件(SQL 语句集合)。
启用事件调度器
-- 查看事件调度器当前状态
SHOW VARIABLES LIKE 'event_scheduler';
结果示例:
| Variable_name | Value |
|---|---|
| event_scheduler | OFF |
-- 临时启用(重启后失效)
SET GLOBAL event_scheduler = ON;
-- 确认已开启
SHOW VARIABLES LIKE 'event_scheduler';
结果示例:
| Variable_name | Value |
|---|---|
| event_scheduler | ON |
-- 永久启用(需修改 my.cnf 配置文件)
-- 在 [mysqld] 段添加:
-- event_scheduler = ON
注意:如果
event_scheduler为DISABLED,表示 MySQL 编译时未启用事件调度器支持,此时无法通过SET GLOBAL开启。
核心语法
CREATE EVENT event_name
ON SCHEDULE
{AT timestamp [+ INTERVAL interval] | EVERY interval [STARTS timestamp] [ENDS timestamp]}
[ON COMPLETION [NOT] PRESERVE]
[ENABLE | DISABLE]
[COMMENT '说明文字']
DO
sql_statement;
| 子句 | 说明 |
|---|---|
AT timestamp | 一次性事件,在指定时间点执行 |
EVERY interval | 周期性事件,按指定间隔重复执行 |
STARTS timestamp | 周期性事件首次执行时间 |
ENDS timestamp | 周期性事件结束时间 |
ON COMPLETION PRESERVE | 事件执行完成后保留,不自动删除 |
ON COMPLETION NOT PRESERVE | 事件执行完成后自动删除(默认) |
ENABLE / DISABLE | 创建时即启用或禁用 |
SQL 示例
场景一:一次性事件(定时清理过期日志)
第一步:建立测试环境
-- 创建日志表
CREATE TABLE logs (
log_id INT AUTO_INCREMENT PRIMARY KEY,
emp_id INT,
action VARCHAR(20),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 插入测试数据(模拟不同时间产生的日志)
INSERT INTO logs (emp_id, action, created_at) VALUES
(1, 'LOGIN', DATE_SUB(NOW(), INTERVAL 10 DAY)),
(2, 'LOGOUT', DATE_SUB(NOW(), INTERVAL 5 DAY)),
(1, 'UPDATE', DATE_SUB(NOW(), INTERVAL 0 DAY));
当前数据状态(logs 表):
| log_id | emp_id | action | created_at |
|---|---|---|---|
| 1 | 1 | LOGIN | 2026-05-31 15:00:00 |
| 2 | 2 | LOGOUT | 2026-06-05 15:00:00 |
| 3 | 1 | UPDATE | 2026-06-10 15:00:00 |
假设当前日期为 2026-06-10,清理策略为删除 7 天前的日志。
第二步:创建一次性清理事件
DELIMITER //
CREATE EVENT IF NOT EXISTS event_delete_old_logs
ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 1 MINUTE
ON COMPLETION NOT PRESERVE
ENABLE
DO
BEGIN
DELETE FROM logs WHERE created_at < DATE_SUB(NOW(), INTERVAL 7 DAY);
END //
DELIMITER ;
第三步:查看事件是否创建成功
SHOW EVENTS WHERE Name = 'event_delete_old_logs';
结果示例:
| Db | Name | Definer | Time zone | Type | Execute at | Interval value | Interval field | Starts | Ends | Status |
|---|---|---|---|---|---|---|---|---|---|---|
| test | event_delete_old_logs | root@localhost | SYSTEM | ONE TIME | 2026-06-10 15:01:00 | NULL | NULL | NULL | NULL | ENABLED |
第四步:等待事件执行后查看结果
-- 1 分钟后查询日志表
SELECT * FROM logs;
操作后的数据状态(logs 表):
| log_id | emp_id | action | created_at |
|---|---|---|---|
| 2 | 2 | LOGOUT | 2026-06-05 15:00:00 |
| 3 | 1 | UPDATE | 2026-06-10 15:00:00 |
结果解读:
log_id = 1的日志因为超过 7 天(created_at < DATE_SUB(NOW(), INTERVAL 7 DAY)),已被事件自动删除。log_id = 2(5 天前)和log_id = 3(当天)符合保留条件,仍然存在。- 由于使用了
ON COMPLETION NOT PRESERVE,事件执行一次后自动从系统中删除。可以再次执行SHOW EVENTS确认该事件已不存在。
场景二:周期性事件(每日日志清理与表优化)
第一步:建立测试环境
-- 继续使用 logs 表,补充更多历史数据
INSERT INTO logs (emp_id, action, created_at) VALUES
(1, 'LOGIN', DATE_SUB(NOW(), INTERVAL 35 DAY)),
(2, 'LOGOUT', DATE_SUB(NOW(), INTERVAL 32 DAY)),
(1, 'DELETE', DATE_SUB(NOW(), INTERVAL 31 DAY)),
(2, 'UPDATE', DATE_SUB(NOW(), INTERVAL 15 DAY)),
(1, 'LOGIN', DATE_SUB(NOW(), INTERVAL 2 DAY));
当前数据状态(logs 表):
| log_id | emp_id | action | created_at |
|---|---|---|---|
| 1 | 1 | LOGIN | 2026-05-31 15:00:00 |
| 2 | 2 | LOGOUT | 2026-06-05 15:00:00 |
| 3 | 1 | UPDATE | 2026-06-10 15:00:00 |
| 4 | 1 | LOGIN | 2026-05-06 15:00:00 |
| 5 | 2 | LOGOUT | 2026-05-09 15:00:00 |
| 6 | 1 | DELETE | 2026-05-10 15:00:00 |
| 7 | 2 | UPDATE | 2026-05-26 15:00:00 |
| 8 | 1 | LOGIN | 2026-06-08 15:00:00 |
清理策略:删除 30 天前的操作日志,并执行表优化。
第二步:创建周期性清理事件
DELIMITER //
CREATE EVENT IF NOT EXISTS event_daily_cleanup
ON SCHEDULE EVERY 1 DAY
STARTS CURRENT_TIMESTAMP + INTERVAL 1 MINUTE
ON COMPLETION PRESERVE
ENABLE
COMMENT '每日清理30天前的日志并优化表'
DO
BEGIN
DELETE FROM logs WHERE created_at < DATE_SUB(NOW(), INTERVAL 30 DAY);
OPTIMIZE TABLE logs;
END //
DELIMITER ;
第三步:查看事件状态
SHOW EVENTS WHERE Name = 'event_daily_cleanup';
结果示例:
| Db | Name | Definer | Time zone | Type | Execute at | Interval value | Interval field | Starts | Ends | Status |
|---|---|---|---|---|---|---|---|---|---|---|
| test | event_daily_cleanup | root@localhost | SYSTEM | RECURRING | NULL | 1 | DAY | 2026-06-10 15:01:00 | NULL | ENABLED |
第四步:手动触发查看效果(或等待首次执行)
-- 等待 1 分钟后查询
SELECT * FROM logs;
操作后的数据状态(logs 表):
| log_id | emp_id | action | created_at |
|---|---|---|---|
| 2 | 2 | LOGOUT | 2026-06-05 15:00:00 |
| 3 | 1 | UPDATE | 2026-06-10 15:00:00 |
| 7 | 2 | UPDATE | 2026-05-26 15:00:00 |
| 8 | 1 | LOGIN | 2026-06-08 15:00:00 |
结果解读:
log_id = 1, 4, 5, 6的日志超过 30 天,已被周期性事件自动清理。log_id = 2, 3, 7, 8的日志在 30 天内,被保留。- 由于使用了
ON COMPLETION PRESERVE,该事件会持续每天执行,直到被手动删除或到达ENDS时间。 OPTIMIZE TABLE会重组表数据,回收碎片空间,提升查询效率。
场景三:定时数据统计(每日报表生成)
第一步:建立测试环境
-- 创建员工表(含入职时间)
CREATE TABLE employees (
emp_id INT AUTO_INCREMENT PRIMARY KEY,
emp_name VARCHAR(20),
dept VARCHAR(20),
score DECIMAL(5,2),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 创建每日统计表
CREATE TABLE daily_stats (
stat_id INT AUTO_INCREMENT PRIMARY KEY,
stat_date DATE,
new_employees INT,
total_employees INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 插入测试数据(模拟不同日期入职的员工)
INSERT INTO employees (emp_name, dept, score, created_at) VALUES
('大翔', '技术部', 100, DATE_SUB(NOW(), INTERVAL 2 DAY)),
('白歌', '技术部', NULL, DATE_SUB(NOW(), INTERVAL 0 DAY));
当前数据状态(employees 表):
| emp_id | emp_name | dept | score | created_at |
|---|---|---|---|---|
| 1 | 大翔 | 技术部 | 100 | 2026-06-08 10:00:00 |
| 2 | 白歌 | 技术部 | NULL | 2026-06-10 11:00:00 |
当前数据状态(daily_stats 表,尚未生成):
SELECT * FROM daily_stats;
结果:空表
第二步:创建定时统计事件
DELIMITER //
CREATE EVENT IF NOT EXISTS event_daily_report
ON SCHEDULE EVERY 1 DAY
STARTS CURRENT_DATE + INTERVAL 1 DAY
ON COMPLETION PRESERVE
ENABLE
COMMENT '每日凌晨统计当日新增员工数和总员工数'
DO
BEGIN
INSERT INTO daily_stats (stat_date, new_employees, total_employees)
SELECT
CURDATE(),
(SELECT COUNT(*) FROM employees WHERE DATE(created_at) = CURDATE()),
(SELECT COUNT(*) FROM employees);
END //
DELIMITER ;
第三步:手动模拟事件执行效果
-- 手动执行事件中的逻辑,查看效果
INSERT INTO daily_stats (stat_date, new_employees, total_employees)
SELECT
CURDATE(),
(SELECT COUNT(*) FROM employees WHERE DATE(created_at) = CURDATE()),
(SELECT COUNT(*) FROM employees);
操作后的数据状态(daily_stats 表):
| stat_id | stat_date | new_employees | total_employees | created_at |
|---|---|---|---|---|
| 1 | 2026-06-10 | 1 | 2 | 2026-06-10 15:00:00 |
结果解读:
stat_date为统计日期(当天)。new_employees = 1表示当天(2026-06-10)新增了 1 名员工(白歌)。total_employees = 2表示员工表当前总记录数为 2。- 该事件配置为从次日开始每天凌晨执行,持续生成每日统计报表。
场景四:带结束时间的周期性事件
第一步:建立测试环境
基于前面已建立的 employees 表。
当前数据状态(employees 表):
| emp_id | emp_name | dept | score | created_at |
|---|---|---|---|---|
| 1 | 大翔 | 技术部 | 100 | 2026-06-08 10:00:00 |
| 2 | 白歌 | 技术部 | NULL | 2026-06-10 11:00:00 |
员工白歌的 score 为 NULL,需要自动修正为 0。
第二步:创建带结束时间的自动修正事件
DELIMITER //
CREATE EVENT IF NOT EXISTS event_fix_null_score
ON SCHEDULE EVERY 1 DAY
STARTS CURRENT_TIMESTAMP + INTERVAL 1 MINUTE
ENDS CURRENT_TIMESTAMP + INTERVAL 30 DAY
ON COMPLETION PRESERVE
ENABLE
COMMENT '每日检查并修正 score 为 NULL 的记录,30天后自动停止'
DO
BEGIN
UPDATE employees
SET score = 0
WHERE score IS NULL;
END //
DELIMITER ;
第三步:查看事件详情
SHOW EVENTS WHERE Name = 'event_fix_null_score';
结果示例:
| Db | Name | Definer | Type | Starts | Ends | Status |
|---|---|---|---|---|---|---|
| test | event_fix_null_score | root@localhost | RECURRING | 2026-06-10 15:01:00 | 2026-07-10 15:00:00 | ENABLED |
第四步:等待事件执行后查看结果
SELECT * FROM employees;
操作后的数据状态(employees 表):
| emp_id | emp_name | dept | score | created_at |
|---|---|---|---|---|
| 1 | 大翔 | 技术部 | 100 | 2026-06-08 10:00:00 |
| 2 | 白歌 | 技术部 | 0 | 2026-06-10 11:00:00 |
结果解读:
emp_id = 2(白歌)的 score 因为IS NULL,已被事件自动修正为0。- 该事件配置了
ENDS CURRENT_TIMESTAMP + INTERVAL 30 DAY,30 天后会自动停止执行并保留在系统中。 - 适用于临时数据修复等需要自动收尾的业务场景。
场景五:禁用、启用和删除事件
基于前面已创建的 event_daily_cleanup 事件,演示管理操作。
查看当前所有事件:
SHOW EVENTS;
结果示例:
| Db | Name | Type | Status |
|---|---|---|---|
| test | event_daily_cleanup | RECURRING | ENABLED |
| test | event_fix_null_score | RECURRING | ENABLED |
禁用事件:
ALTER EVENT event_daily_cleanup DISABLE;
-- 确认已禁用
SHOW EVENTS WHERE Name = 'event_daily_cleanup';
结果示例:
| Db | Name | Type | Status |
|---|---|---|---|
| test | event_daily_cleanup | RECURRING | DISABLED |
启用事件:
ALTER EVENT event_daily_cleanup ENABLE;
-- 确认已启用
SHOW EVENTS WHERE Name = 'event_daily_cleanup';
结果示例:
| Db | Name | Type | Status |
|---|---|---|---|
| test | event_daily_cleanup | RECURRING | ENABLED |
修改事件执行计划:
ALTER EVENT event_daily_cleanup
ON SCHEDULE EVERY 12 HOUR
STARTS CURRENT_TIMESTAMP + INTERVAL 10 MINUTE;
查看事件创建语句:
SHOW CREATE EVENT event_daily_cleanup;
删除事件:
DROP EVENT IF EXISTS event_daily_cleanup;
-- 确认已删除
SHOW EVENTS WHERE Name = 'event_daily_cleanup';
结果:无记录返回,事件已删除。
结果解读:
ALTER EVENT ... DISABLE/ENABLE可以临时停用或恢复事件,常用于维护窗口期间暂停定时任务。ALTER EVENT还可以修改事件的调度计划、执行内容等。DROP EVENT IF EXISTS安全删除事件,IF EXISTS避免事件不存在时报错。- 删除后该事件将不再执行,已产生的数据不受影响。
场景六:事件执行日志记录(最佳实践)
第一步:建立测试环境
-- 创建事件执行日志表
CREATE TABLE event_execution_logs (
log_id INT AUTO_INCREMENT PRIMARY KEY,
event_name VARCHAR(100),
start_time DATETIME,
end_time DATETIME,
rows_affected INT,
status VARCHAR(20),
error_message TEXT
);
-- 继续使用 logs 表,补充测试数据
INSERT INTO logs (emp_id, action, created_at) VALUES
(1, 'OLD_LOGIN', DATE_SUB(NOW(), INTERVAL 10 DAY)),
(2, 'OLD_LOGOUT', DATE_SUB(NOW(), INTERVAL 8 DAY)),
(1, 'NEW_LOGIN', DATE_SUB(NOW(), INTERVAL 1 DAY));
当前数据状态(logs 表):
| log_id | emp_id | action | created_at |
|---|---|---|---|
| 1 | 1 | LOGIN | 2026-05-31 15:00:00 |
| 2 | 2 | LOGOUT | 2026-06-05 15:00:00 |
| 3 | 1 | UPDATE | 2026-06-10 15:00:00 |
| 7 | 2 | UPDATE | 2026-05-26 15:00:00 |
| 8 | 1 | LOGIN | 2026-06-08 15:00:00 |
| 9 | 1 | OLD_LOGIN | 2026-05-31 15:00:00 |
| 10 | 2 | OLD_LOGOUT | 2026-06-02 15:00:00 |
| 11 | 1 | NEW_LOGIN | 2026-06-09 15:00:00 |
第二步:创建带日志记录的事件
DELIMITER //
CREATE EVENT IF NOT EXISTS event_cleanup_with_log
ON SCHEDULE EVERY 1 DAY
STARTS CURRENT_TIMESTAMP + INTERVAL 1 MINUTE
ON COMPLETION PRESERVE
ENABLE
DO
BEGIN
DECLARE v_start DATETIME DEFAULT NOW();
DECLARE v_rows INT DEFAULT 0;
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
INSERT INTO event_execution_logs (event_name, start_time, end_time, status, error_message)
VALUES ('event_cleanup_with_log', v_start, NOW(), 'FAILED', SQLERRM());
END;
DELETE FROM logs WHERE created_at < DATE_SUB(NOW(), INTERVAL 7 DAY);
SET v_rows = ROW_COUNT();
INSERT INTO event_execution_logs (event_name, start_time, end_time, rows_affected, status)
VALUES ('event_cleanup_with_log', v_start, NOW(), v_rows, 'SUCCESS');
END //
DELIMITER ;
第三步:等待事件执行后查看结果
SELECT * FROM logs;
操作后的数据状态(logs 表):
| log_id | emp_id | action | created_at |
|---|---|---|---|
| 2 | 2 | LOGOUT | 2026-06-05 15:00:00 |
| 3 | 1 | UPDATE | 2026-06-10 15:00:00 |
| 7 | 2 | UPDATE | 2026-05-26 15:00:00 |
| 8 | 1 | LOGIN | 2026-06-08 15:00:00 |
| 11 | 1 | NEW_LOGIN | 2026-06-09 15:00:00 |
SELECT * FROM event_execution_logs;
操作后的数据状态(event_execution_logs 表):
| log_id | event_name | start_time | end_time | rows_affected | status | error_message |
|---|---|---|---|---|---|---|
| 1 | event_cleanup_with_log | 2026-06-10 15:01:00 | 2026-06-10 15:01:00 | 3 | SUCCESS | NULL |
结果解读:
logs表中超过 7 天的 3 条记录(log_id = 1, 9, 10)已被清理。event_execution_logs表自动记录了事件的执行时间、影响行数和状态。- 使用
DECLARE EXIT HANDLER FOR SQLEXCEPTION捕获异常,即使执行失败也会记录错误信息,便于后续排查。 - 这是事件调度器的最佳实践:务必自行记录执行日志,因为 MySQL 不会为事件保留内置执行历史。
事件调度器的限制
| 限制 | 说明 |
|---|---|
| 依赖 MySQL 进程 | MySQL 停止时事件不会执行,重启后也不会补执行 |
| 调度粒度 | 最小间隔是 1 秒,不支持 Cron 表达式(如"每周一 3 点"需要额外处理) |
| 无内置历史记录 | 执行成功或失败不会在 MySQL 中留下日志,需自行记录 |
| 主从复制 | 默认事件在主库执行,从库不执行(super_read_only 时) |
常见误区
| 误区 | 正解 |
|---|---|
| 事件调度器可以替代 Linux Cron | 简单任务可以,但复杂调度(如"每月最后一个周五")、外部脚本调用仍需 Cron。 |
| 事件执行失败会通知我 | 不会。MySQL 不会主动通知事件执行失败,需自行在事件逻辑中记录日志或发送告警。 |
| 事件调度器默认开启 | 默认是关闭的,需要手动开启 event_scheduler。 |
面试考点
Q:MySQL 事件调度器和 Linux Cron 的区别?
事件调度器是 MySQL 内置的,适合纯数据库操作(清理、统计),无需外部依赖;Cron 是操作系统级的,适合调用外部脚本、更复杂的调度逻辑。事件调度器在 MySQL 停止时不执行。
Q:如何确保事件在主从架构下只执行一次?
MySQL 5.7 中,事件默认在主库执行,从库不执行(因为
super_read_only阻止了事件的写入)。但需谨慎验证,避免双主架构下的重复执行。
Q:事件执行失败怎么排查?
MySQL 错误日志可能记录事件执行错误,但信息有限。最佳实践是在事件逻辑中加入异常处理(
DECLARE CONTINUE HANDLER)并将执行结果写入日志表。
小结
- 事件调度器让 MySQL 具备定时执行任务的能力
- 支持一次性事件(
AT)和周期性事件(EVERY) - 默认关闭,需启用
event_scheduler - 适合数据库内部维护任务,复杂调度仍需外部工具
- 最佳实践:在事件内部记录执行日志,监控执行状态和影响行数
下一章引子:数据库自动化能力讲完了,接下来进入数据库安全领域——用户管理和权限控制。