CREATE EVENT
CREATE EVENT define una tarea programada que MySQL ejecuta automáticamente en un momento determinado o de forma periódica. Es el equivalente a un cron job dentro del propio servidor de base de datos: puedes programar purgas de datos antiguos, recalcular estadísticas, generar resúmenes o ejecutar cualquier sentencia SQL sin intervención manual.
Los eventos programados son una de las funcionalidades más útiles y a la vez menos conocidas de MySQL. Mientras que muchos equipos recurren a scripts externos ejecutados desde el sistema operativo, el Event Scheduler permite mantener toda la lógica dentro del servidor de base de datos, lo que simplifica la administración y garantiza que las tareas se ejecuten aunque el servidor de aplicación esté caído.
Activar el Event Scheduler
Antes de crear cualquier evento, necesitas asegurarte de que el planificador de eventos está activo. Por defecto, MySQL lo trae desactivado. El Event Scheduler es un hilo de segundo plano que revisa continuamente si hay eventos pendientes de ejecutar. Si está apagado, tus eventos existirán en la base de datos pero nunca se dispararán.
Para activarlo en la sesión actual y en todas las conexiones del servidor, ejecuta lo siguiente:
SET GLOBAL event_scheduler = ON;Esta instrucción requiere el privilegio SUPER o SYSTEM_VARIABLES_ADMIN. Si no lo tienes, contacta con el administrador del servidor. Puedes verificar que el scheduler está activo consultando la variable global correspondiente:
SHOW VARIABLES LIKE 'event_scheduler';El resultado debe mostrar ON. Si aparece OFF, el scheduler está detenido. Si aparece DISABLED, fue desactivado desde el arranque del servidor con la opción --event-scheduler=DISABLED y no podrás activarlo dinámicamente; necesitarás modificar el archivo de configuración my.cnf y reiniciar MySQL.
Para que la activación persista tras un reinicio del servidor, añade esta línea a la sección [mysqld] del archivo my.cnf:
[mysqld]
event_scheduler = ONTambién puedes comprobar que el hilo del scheduler está corriendo consultando la lista de procesos:
SHOW PROCESSLIST;Deberías ver una fila con el usuario event_scheduler en la columna User y el comando Daemon en la columna Command. Si esa fila no aparece, el scheduler no está funcionando.
Sintaxis de CREATE EVENT
La sintaxis completa de CREATE EVENT es la siguiente:
CREATE
[DEFINER = usuario]
EVENT
[IF NOT EXISTS]
nombre_evento
ON SCHEDULE programacion
[ON COMPLETION [NOT] PRESERVE]
[ENABLE | DISABLE | DISABLE ON SLAVE]
[COMMENT 'descripcion']
DO cuerpo_evento;Cada cláusula tiene un propósito específico. La clave está en la programación (ON SCHEDULE), que define cuándo se ejecuta el evento, y en el cuerpo (DO), que contiene las sentencias SQL que se ejecutarán. Veamos cada parte en detalle.
Eventos puntuales con AT
Un evento puntual se ejecuta una sola vez en un momento determinado. Se define con la cláusula AT seguida de una expresión de fecha y hora. Una vez ejecutado, el evento desaparece de la lista de eventos activos (salvo que uses ON COMPLETION PRESERVE).
CREATE EVENT limpiar_sesiones_expiradas
ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 1 HOUR
DO
DELETE FROM sesiones WHERE fecha_expiracion < NOW();Este evento se ejecutará exactamente una hora después de su creación. MySQL evaluará CURRENT_TIMESTAMP + INTERVAL 1 HOUR en el momento de crear el evento y almacenará la fecha resultante como momento de ejecución.
Puedes usar cualquier expresión que devuelva un DATETIME. Por ejemplo, para programar una tarea a una fecha concreta:
CREATE EVENT generar_informe_trimestral
ON SCHEDULE AT '2026-04-01 02:00:00'
DO
INSERT INTO informes_trimestrales (trimestre, total_ventas, total_clientes, fecha_generacion)
SELECT
'2026-Q1',
SUM(total),
COUNT(DISTINCT cliente_id),
NOW()
FROM pedidos
WHERE fecha_pedido BETWEEN '2026-01-01' AND '2026-03-31';Los intervalos disponibles para la aritmética de fechas incluyen SECOND, MINUTE, HOUR, DAY, WEEK, MONTH, QUARTER y YEAR. También puedes combinar intervalos con sumas sucesivas: CURRENT_TIMESTAMP + INTERVAL 2 DAY + INTERVAL 6 HOUR.
Eventos recurrentes con EVERY
Los eventos recurrentes son los más habituales en entornos de producción. Se definen con la cláusula EVERY y se ejecutan de forma periódica mientras estén activos. Opcionalmente, puedes definir una fecha de inicio con STARTS y una fecha de fin con ENDS.
CREATE EVENT purgar_logs_antiguos
ON SCHEDULE EVERY 1 DAY
STARTS CURRENT_TIMESTAMP
ENDS CURRENT_TIMESTAMP + INTERVAL 1 YEAR
DO
DELETE FROM logs_aplicacion
WHERE fecha_creacion < NOW() - INTERVAL 90 DAY
LIMIT 10000;Este evento se ejecuta cada día desde el momento de su creación y durante un año. Cada ejecución elimina hasta 10.000 registros de logs con más de 90 días de antigüedad. Usar LIMIT es una buena práctica para evitar bloqueos largos en tablas grandes; si hay más de 10.000 filas que eliminar, la siguiente ejecución se encargará del resto.
Si omites STARTS, el evento comienza a ejecutarse inmediatamente. Si omites ENDS, el evento se ejecuta indefinidamente (o hasta que lo elimines o deshabilites manualmente). Los intervalos disponibles para EVERY son los mismos que para AT.
Un ejemplo con un intervalo más corto, útil para mantener datos actualizados:
CREATE EVENT recalcular_estadisticas_productos
ON SCHEDULE EVERY 15 MINUTE
DO
REPLACE INTO estadisticas_productos (producto_id, total_ventas, cantidad_vendida, ultima_actualizacion)
SELECT
p.id,
COALESCE(SUM(dp.subtotal), 0),
COALESCE(SUM(dp.cantidad), 0),
NOW()
FROM productos p
LEFT JOIN detalle_pedidos dp ON dp.producto_id = p.id
LEFT JOIN pedidos pe ON pe.id = dp.pedido_id AND pe.estado = 'completado'
GROUP BY p.id;Este evento recalcula las estadísticas de ventas cada 15 minutos. Usa REPLACE INTO para que los registros existentes se actualicen y los nuevos se inserten automáticamente.
ON COMPLETION PRESERVE vs NOT PRESERVE
La cláusula ON COMPLETION controla qué ocurre con la definición del evento una vez que ha finalizado su ejecución (en eventos puntuales) o cuando ha pasado la fecha ENDS (en eventos recurrentes).
Con ON COMPLETION NOT PRESERVE (el valor por defecto), MySQL elimina automáticamente la definición del evento cuando este deja de estar programado. El evento desaparece por completo, como si hubieras ejecutado un DROP EVENT.
Con ON COMPLETION PRESERVE, MySQL mantiene la definición del evento en la base de datos aunque ya no esté programado para ejecutarse. El evento queda con estado DISABLED, pero puedes consultarlo, inspeccionarlo y reactivarlo con ALTER EVENT.
CREATE EVENT auditoria_fin_de_mes
ON SCHEDULE AT '2026-03-31 23:59:59'
ON COMPLETION PRESERVE
DO
INSERT INTO auditoria_mensual (mes, total_operaciones, total_errores, fecha_auditoria)
SELECT
DATE_FORMAT(NOW(), '%Y-%m'),
COUNT(*),
SUM(CASE WHEN estado = 'error' THEN 1 ELSE 0 END),
NOW()
FROM operaciones
WHERE MONTH(fecha) = MONTH(NOW()) AND YEAR(fecha) = YEAR(NOW());Usar PRESERVE es recomendable cuando quieres mantener un historial de eventos que ya se ejecutaron, o cuando necesitas la definición como referencia para crear eventos similares en el futuro. En cambio, NOT PRESERVE mantiene limpia la lista de eventos, eliminando automáticamente los que ya no son necesarios.
ENABLE, DISABLE y DISABLE ON SLAVE
Puedes controlar el estado inicial de un evento con estas cláusulas. Por defecto, un evento se crea habilitado (ENABLE), lo que significa que se ejecutará según su programación.
CREATE EVENT evento_prueba
ON SCHEDULE EVERY 1 HOUR
DISABLE
COMMENT 'Evento en pruebas, activar cuando esté verificado'
DO
CALL procedimiento_experimental();Este evento se crea deshabilitado. Puedes probarlo todo lo que necesites y activarlo más adelante con ALTER EVENT evento_prueba ENABLE.
La opción DISABLE ON SLAVE es específica para entornos de replicación. Cuando un evento se replica a un servidor esclavo, normalmente no quieres que se ejecute también allí (porque las modificaciones de datos ya se replican a través del binlog). DISABLE ON SLAVE indica que el evento solo debe ejecutarse en el servidor maestro y permanecer desactivado en los esclavos.
Cuerpos con varias sentencias
Cuando el evento necesita ejecutar más de una sentencia SQL, debes usar un bloque BEGIN...END. Para que MySQL no confunda los puntos y coma internos del bloque con el fin de la sentencia CREATE EVENT, necesitas cambiar el delimitador temporalmente.
DELIMITER //
CREATE EVENT resumen_diario_ventas
ON SCHEDULE EVERY 1 DAY
STARTS '2026-03-25 01:00:00'
COMMENT 'Genera resumen diario de ventas y notifica si hay anomalías'
DO
BEGIN
DECLARE v_total_hoy DECIMAL(12,2);
DECLARE v_promedio_semana DECIMAL(12,2);
-- Calcular total de ventas del día anterior
SELECT COALESCE(SUM(total), 0) INTO v_total_hoy
FROM pedidos
WHERE DATE(fecha_pedido) = CURDATE() - INTERVAL 1 DAY
AND estado IN ('completado', 'enviado');
-- Calcular promedio de la última semana
SELECT COALESCE(AVG(total_diario), 0) INTO v_promedio_semana
FROM (
SELECT DATE(fecha_pedido) AS dia, SUM(total) AS total_diario
FROM pedidos
WHERE fecha_pedido >= CURDATE() - INTERVAL 8 DAY
AND fecha_pedido < CURDATE() - INTERVAL 1 DAY
AND estado IN ('completado', 'enviado')
GROUP BY DATE(fecha_pedido)
) AS ultimos_dias;
-- Insertar resumen
INSERT INTO resumenes_diarios (fecha, total_ventas, promedio_semanal, desviacion_porcentaje)
VALUES (
CURDATE() - INTERVAL 1 DAY,
v_total_hoy,
v_promedio_semana,
CASE
WHEN v_promedio_semana > 0
THEN ROUND(((v_total_hoy - v_promedio_semana) / v_promedio_semana) * 100, 2)
ELSE 0
END
);
-- Registrar alerta si las ventas cayeron más del 30%
IF v_promedio_semana > 0 AND v_total_hoy < v_promedio_semana * 0.7 THEN
INSERT INTO alertas (tipo, mensaje, fecha_creacion)
VALUES (
'ventas_bajas',
CONCAT('Ventas del ', DATE_FORMAT(CURDATE() - INTERVAL 1 DAY, '%d/%m/%Y'),
': ', FORMAT(v_total_hoy, 2), '€ (',
ROUND(((v_total_hoy - v_promedio_semana) / v_promedio_semana) * 100, 1),
'% vs promedio semanal)'),
NOW()
);
END IF;
END //
DELIMITER ;Este ejemplo ilustra la potencia real de los eventos programados. El evento se ejecuta cada día a la 1:00 AM, calcula las ventas del día anterior, las compara con el promedio semanal y genera una alerta automática si detecta una caída significativa. Todo esto ocurre dentro del servidor MySQL sin necesidad de ningún script externo.
Ejemplo: purgar registros por lotes
En tablas con millones de registros, eliminar grandes cantidades de datos de una sola vez puede bloquear la tabla durante minutos. Una estrategia eficaz es crear un evento que elimine registros en lotes pequeños con alta frecuencia:
DELIMITER //
CREATE EVENT purgar_notificaciones_leidas
ON SCHEDULE EVERY 5 MINUTE
COMMENT 'Elimina notificaciones leídas con más de 60 días en lotes de 5000'
DO
BEGIN
DECLARE v_eliminadas INT DEFAULT 1;
-- Eliminar hasta 3 lotes de 5000 por ejecución
WHILE v_eliminadas > 0 DO
DELETE FROM notificaciones
WHERE leida = TRUE
AND fecha_lectura < NOW() - INTERVAL 60 DAY
LIMIT 5000;
SET v_eliminadas = ROW_COUNT();
END WHILE;
END //
DELIMITER ;El bucle WHILE continúa eliminando lotes de 5.000 filas hasta que no quedan registros que cumplan la condición. Cada ejecución del evento (cada 5 minutos) procesa todo lo que pueda. Los LIMIT pequeños mantienen los bloqueos cortos y permiten que otras consultas se intercalen entre las eliminaciones.
Ejemplo: enviar resúmenes semanales
Un caso de uso habitual es generar datos de resumen al final de cada semana. Aunque MySQL no puede enviar correos electrónicos directamente, puede preparar los datos en una tabla que luego una aplicación externa consulte para enviar las notificaciones:
DELIMITER //
CREATE EVENT resumen_semanal_clientes
ON SCHEDULE EVERY 1 WEEK
STARTS '2026-03-30 06:00:00'
ON COMPLETION PRESERVE
COMMENT 'Genera resumen semanal de actividad por cliente'
DO
BEGIN
INSERT INTO resumenes_semanales (
cliente_id, semana, total_pedidos, importe_total,
producto_mas_comprado, fecha_generacion
)
SELECT
c.id,
YEARWEEK(CURDATE() - INTERVAL 1 WEEK, 1),
COUNT(p.id),
COALESCE(SUM(p.total), 0),
(
SELECT dp2.producto_id
FROM detalle_pedidos dp2
JOIN pedidos p2 ON p2.id = dp2.pedido_id
WHERE p2.cliente_id = c.id
AND p2.fecha_pedido >= CURDATE() - INTERVAL 1 WEEK
GROUP BY dp2.producto_id
ORDER BY SUM(dp2.cantidad) DESC
LIMIT 1
),
NOW()
FROM clientes c
LEFT JOIN pedidos p ON p.cliente_id = c.id
AND p.fecha_pedido >= CURDATE() - INTERVAL 1 WEEK
AND p.fecha_pedido < CURDATE()
WHERE c.activo = TRUE
GROUP BY c.id;
END //
DELIMITER ;El evento arranca el lunes 30 de marzo de 2026 a las 6:00 AM y se repite cada semana. Usa ON COMPLETION PRESERVE para que la definición se conserve incluso si en el futuro decides deshabilitarlo temporalmente.
Privilegios necesarios
Para crear eventos necesitas el privilegio EVENT en la base de datos donde quieras crearlos. Un administrador puede concederlo así:
GRANT EVENT ON tienda.* TO 'usuario_app'@'localhost';Si necesitas que el evento ejecute sentencias que requieren privilegios especiales (como acceder a otras bases de datos o modificar variables globales), el usuario definido en la cláusula DEFINER debe tener esos privilegios. Por defecto, el DEFINER es el usuario que crea el evento.
Errores comunes
Uno de los errores más frecuentes es crear un evento y que nunca se ejecute. Casi siempre la causa es que el Event Scheduler está desactivado. Antes de buscar problemas en la lógica del evento, verifica siempre el estado del scheduler con SHOW VARIABLES LIKE 'event_scheduler'.
Otro error habitual es programar un evento puntual con AT usando una fecha que ya ha pasado. MySQL creará el evento pero lo ejecutará inmediatamente (si la fecha está en el pasado pero dentro de un margen de tolerancia) o simplemente no lo ejecutará y lo descartará. Siempre usa expresiones relativas como CURRENT_TIMESTAMP + INTERVAL para evitar este problema.
Si olvidas cambiar el delimitador al usar BEGIN...END, MySQL interpretará el primer punto y coma dentro del bloque como el fin de la sentencia CREATE EVENT, provocando un error de sintaxis. Consulta el artículo sobre DELIMITER para entender este mecanismo en profundidad.
Intentar crear un evento con un nombre que ya existe genera el error 1537 (Event already exists). Usa IF NOT EXISTS para evitarlo:
CREATE EVENT IF NOT EXISTS mi_evento
ON SCHEDULE EVERY 1 HOUR
DO
DELETE FROM cache_temporal WHERE creado < NOW() - INTERVAL 1 HOUR;Cuándo usar eventos programados
Los eventos programados son ideales para tareas de mantenimiento que deben ejecutarse de forma regular y predecible: purgar datos antiguos, recalcular agregados, archivar registros, generar informes periódicos o verificar la integridad de los datos. Su principal ventaja es que viven dentro del servidor MySQL, lo que elimina dependencias externas y garantiza que se ejecuten mientras el servidor esté activo.
Sin embargo, no son la mejor opción para tareas que necesitan interactuar con sistemas externos (enviar correos, llamar a APIs, generar archivos en disco). Para esas situaciones, es mejor usar un planificador externo como cron que invoque un script con la lógica necesaria. Los eventos de MySQL tampoco son ideales para tareas que requieren lógica condicional muy compleja o que necesitan manejar errores de forma sofisticada, ya que las opciones de control de errores dentro de un evento son limitadas comparadas con un lenguaje de programación completo.
Escrito por Eduardo Lázaro
