MySQL – Scheduled Events

December 2, 20244 min readUpdated 8/25/2026

An event is a scheduled job that runs inside MySQL. It is cron, without cron — the server keeps the schedule and executes the SQL itself.

Check the scheduler first

SELECT @@event_scheduler AS event_scheduler;
+-----------------+
| event_scheduler |
+-----------------+
| ON              |
+-----------------+

It is ON by default in MySQL 8. A great deal of advice says otherwise — "your event never ran because the scheduler is off" — and that was true of MySQL 5.x, where the default was OFF. Check rather than assume, in both directions: a managed database may well have turned it off again.

SET GLOBAL event_scheduler = ON;    -- until restart
SET PERSIST event_scheduler = ON;   -- and afterwards (MySQL 8)

With it OFF, events are stored and simply never fire. No error, no warning.

A recurring event

The pizza schema keeps server-side carts, which accumulate: someone builds an order, wanders off, and the rows stay forever. A nightly clean-up:

CREATE EVENT ev_purge_abandoned_carts
ON SCHEDULE EVERY 1 DAY
STARTS '2026-01-01 03:00:00'
ON COMPLETION PRESERVE
COMMENT 'Delete carts untouched for 30 days'
DO
    DELETE FROM cart WHERE updated_at < NOW() - INTERVAL 30 DAY;

ON COMPLETION PRESERVE matters more than it looks. The default is NOT PRESERVE, which drops the event once it can no longer run — fine for a one-off, and for a recurring event with an ENDS clause it means the definition disappears when it finishes.

STARTS pins the first run. Without it, "every 1 day" means every 24 hours counted from whenever you created it, so the job drifts to whatever time you happened to deploy.

A one-off

CREATE EVENT ev_one_off_cleanup
ON SCHEDULE AT '2026-03-01 02:00:00'
DO
    DELETE FROM order_status_audit WHERE changed_at < '2025-01-01';

Useful for scheduling a heavy migration for a quiet hour. It deletes itself afterwards, which for a one-off is what you want.

Several statements

DELIMITER $$

CREATE EVENT ev_nightly_rollup
ON SCHEDULE EVERY 1 DAY
STARTS '2026-01-01 04:00:00'
ON COMPLETION PRESERVE
DO
BEGIN
    DELETE FROM cart WHERE updated_at < NOW() - INTERVAL 30 DAY;
    DELETE FROM order_status_audit WHERE changed_at < NOW() - INTERVAL 1 YEAR;
END$$

DELIMITER ;

Same DELIMITER requirement as a stored procedure, for the same reason. A neater pattern is to put the work in a procedure and have the event just CALL it — the logic then lives somewhere you can test and run by hand.

Managing them

SHOW EVENTS;
SELECT EVENT_NAME, STATUS, INTERVAL_VALUE, INTERVAL_FIELD, LAST_EXECUTED
FROM   information_schema.EVENTS WHERE EVENT_SCHEMA = 'pizza';

ALTER EVENT ev_purge_abandoned_carts DISABLE;
ALTER EVENT ev_purge_abandoned_carts ENABLE;
DROP EVENT IF EXISTS ev_purge_abandoned_carts;

LAST_EXECUTED is the field to check when someone asks whether the job is running. DISABLE is better than dropping when you want it back.

Where the errors go

This is the operational weak point. An event that fails writes to the server error log and nowhere else — nothing tells your application, your monitoring, or you. A nightly clean-up can fail every night for a month in complete silence.

If you use events, have the job record its own outcome:

CREATE TABLE job_run (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    job_name VARCHAR(60) NOT NULL,
    ran_at   DATETIME(6) NOT NULL,
    rows_affected INT
);

Then alert on the absence of a recent row. "The job did not run" is otherwise invisible, and it is the failure that actually happens.

On a replica

Events do not execute on a replica — the scheduler is disabled there, and the work arrives through replication instead, which is correct. But it means an event's status is SLAVESIDE_DISABLED on the replica, and after a failover you must remember to enable the scheduler on the promoted server. Otherwise every scheduled job silently stops, and nothing reports it. See replication.

Should this be an event?

An event is reasonableUse your scheduler instead
Pure SQL clean-up, close to the dataThe job calls an API or writes a file
You have no other schedulerYou already run cron, Kubernetes CronJobs or Quartz
The work must follow the database on failoverYou need alerting, retries and logs

The honest summary: events are convenient and operationally weak. Most teams already have a scheduler with logging, alerting, retries and version control, and a job defined there calling a stored procedure gets the best of both — the SQL stays next to the data, and the scheduling stays somewhere you can see it.

The one thing an event genuinely gives you is that it travels with the database. If the database is the only piece of infrastructure you can rely on, that counts for something.

What to remember

  • event_scheduler is ON by default in MySQL 8, unlike 5.x. Check it.
  • ON COMPLETION PRESERVE, or the definition disappears when it finishes.
  • STARTS, or the schedule drifts to whenever you deployed.
  • Errors go to the server error log only. Record your own outcomes and alert on absence.
  • Disabled on replicas — re-enable the scheduler after a failover.