Урок доступен в видеоформате 

Планировщик событий

В реальных приложениях часто возникает необходимость выполнять определённые действия автоматически и по расписанию: очищать старые записи, обновлять статистику, формировать отчёты.

Для таких задач MySQL предоставляет механизм запланированных событий.

События в MySQL похожи на планировщик задач в операционной системе: вы создаёте задачу один раз, а база данных выполняет её автоматически по расписанию.

Для таких задач в PostgreSQL используют расширение pg_cron. Оно позволяет создавать запланированные задачи с помощью синтаксиса cron (как в Unix-системах).

Когда он пригодится

Запланированные события помогают автоматизировать следующие задачи:

С помощью pg_cron можно автоматизировать:

  • Очистку данных: удаление устаревших записей логов или временных данных
  • Обновление статистики: пересчёт агрегированных данных для аналитики
  • Генерацию отчётов: автоматическое формирование периодических отчётов
  • Обслуживание данных: перенос записей в архивные таблицы и другие регламентные операции

Как включить планировщик

Прежде чем создавать события, необходимо убедиться, что планировщик событий включён:

MySQL 8.1
SHOW VARIABLES LIKE 'event_scheduler';

Если планировщик выключен, включите его:

MySQL 8.1
SET GLOBAL event_scheduler = ON;

Эта команда меняет настройку до следующего перезапуска сервера и требует права на изменение глобальных системных переменных. Для постоянного включения планировщика используют конфигурацию сервера или SET PERSIST, если команда доступна в используемой версии MySQL.

Сначала pg_cron нужно установить на сервер, добавить в shared_preload_libraries и перезапустить PostgreSQL. После этого расширение создают в базе данных:

MySQL 8.1
CREATE EXTENSION IF NOT EXISTS pg_cron;

Одноразовый запуск

Начнём с самого простого — события, которое выполнится один раз в определённое время:

Начнём с самого простого — задачи, которая выполнится один раз в определённое время:

MySQL 8.1
CREATE EVENT cleanup_old_logs
ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 1 DAY
DO
    DELETE FROM logs WHERE created_at < NOW() - INTERVAL 30 DAY;

Это событие удалит записи логов старше 30 дней через 24 часа после создания события.

Разберём синтаксис:

  • CREATE EVENT cleanup_old_logs — создаём событие с именем cleanup_old_logs
  • ON SCHEDULE AT — указываем, когда событие должно выполниться
  • CURRENT_TIMESTAMP + INTERVAL 1 DAY — время выполнения (через 1 день)
  • DO — тело события: один простой SQL-оператор или составной оператор BEGIN ... END

В pg_cron нет отдельного типа расписания для однократного запуска. Для такой задачи используют внешний планировщик приложения или создают временную задачу и удаляют её после выполнения.

Например, эта задача при ближайшем наступлении 3:00 удалит старые записи логов, а затем удалит саму себя:

MySQL 8.1
SELECT cron.schedule(
    'cleanup_old_logs_once',
    '0 3 * * *',
    $command$
    DO $$
    BEGIN
        DELETE FROM logs WHERE created_at < NOW() - INTERVAL '30 days';
        PERFORM cron.unschedule('cleanup_old_logs_once');
    END;
    $$;
    $command$
);

Обычно расписание '0 3 * * *' запускает задачу каждый день в 3:00. Но после первого выполнения задача вызывает cron.unschedule() для собственного имени, поэтому повторного запуска не будет.

Периодический запуск

Чаще всего события нужно запускать периодически — каждый день, час или минуту:

Чаще всего задачи нужно запускать периодически — каждый день, час или минуту:

MySQL 8.1
CREATE EVENT update_statistics
ON SCHEDULE EVERY 1 HOUR
DO
BEGIN
    UPDATE product_stats SET
        total_sales = (SELECT SUM(amount) FROM orders WHERE product_id = product_stats.product_id),
        last_updated = NOW();
END;

Это событие будет обновлять статистику продаж каждый час.

Разберём синтаксис:

  • ON SCHEDULE EVERY 1 HOUR — выполнять каждый час
  • BEGIN ... END — составной оператор, в который при необходимости можно добавить несколько SQL-операторов

Варианты интервалов:

  • EVERY 1 MINUTE — каждую минуту
  • EVERY 1 HOUR — каждый час
  • EVERY 1 DAY — каждый день
  • EVERY 1 WEEK — каждую неделю
  • EVERY 1 MONTH — каждый месяц
  • EVERY 30 SECOND — каждые 30 секунд
MySQL 8.1
SELECT cron.schedule(
    'cleanup_old_logs',
    '0 3 * * *',
    'DELETE FROM logs WHERE created_at < NOW() - INTERVAL ''30 days'''
);

Эта задача будет выполняться каждый день в 3:00 и удалять записи логов старше 30 дней.

Разберём синтаксис:

  • cron.schedule() — функция для создания запланированной задачи
  • 'cleanup_old_logs' — имя задачи
  • '0 3 * * *' — расписание в формате cron (минута, час, день месяца, месяц, день недели)
  • последний параметр — SQL-команда для выполнения

Формат cron-расписания:

Формат cron расписания

MySQL 8.1
SELECT cron.schedule(
    'update_statistics_hourly',
    '0 * * * *',
    $$
    UPDATE product_stats SET
        total_sales = (SELECT SUM(amount) FROM orders WHERE product_id = product_stats.product_id),
        last_updated = NOW()
    $$
);

Эта задача будет обновлять статистику продаж каждый час (в начале каждого часа).

Примеры расписаний:

  • '*/5 * * * *' — каждые 5 минут
  • '0 * * * *' — каждый час (в начале часа)
  • '0 0 * * *' — каждый день в полночь
  • '0 0 * * 0' — каждое воскресенье в полночь
  • '0 9 1 * *' — первого числа каждого месяца в 9:00
  • '30 seconds' — каждые 30 секунд в pg_cron 1.5 и новее

Интервал в секундах задаётся отдельной строкой, а не шестым полем cron-выражения. pg_cron поддерживает значения от 1 до 59 секунд.

Ограничение периода выполнения

Иногда нужно, чтобы событие работало только в течение определённого периода.

Иногда нужно, чтобы задача работала только в течение определённого периода.

MySQL 8.1
CREATE EVENT temporary_log_cleanup
ON SCHEDULE EVERY 1 DAY
STARTS CURRENT_TIMESTAMP
ENDS CURRENT_TIMESTAMP + INTERVAL 30 DAY
DO
    DELETE FROM logs WHERE created_at < NOW() - INTERVAL 30 DAY;

Это событие будет удалять устаревшие логи раз в день в течение 30 дней.

Новые элементы:

  • STARTS — начало периода действия события
  • ENDS — конец периода действия события

После ENDS событие прекратит выполняться и по умолчанию будет удалено. Чтобы сохранить его определение, при создании нужно добавить ON COMPLETION PRESERVE.

В расписании pg_cron нельзя указать дату автоматического завершения задачи. Например, задачу для ежедневной очистки логов в течение ближайших 30 дней создают с обычным расписанием:

MySQL 8.1
SELECT cron.schedule(
    'temporary_log_cleanup',
    '0 0 * * *',
    $$DELETE FROM logs WHERE created_at < NOW() - INTERVAL '30 days'$$
);

Чтобы задача остановилась автоматически, используют тот же приём, что и в примере одноразового запуска выше: создают задачу stop_temporary_log_cleanup на дату через 30 дней. Тело команды для неё выглядит так:

MySQL 8.1
DO $$
BEGIN
    PERFORM cron.unschedule('temporary_log_cleanup');
    PERFORM cron.unschedule('stop_temporary_log_cleanup');
END;
$$;

Первый вызов удаляет задачу очистки, а второй — служебную задачу. В расписании служебной задачи указывают минуту, час, день и месяц даты, которая наступит через 30 дней.

Просмотр расписания

Чтобы увидеть все созданные события:

MySQL 8.1
SHOW EVENTS;

Для просмотра событий в конкретной базе данных:

MySQL 8.1
SHOW EVENTS FROM your_database_name;

Чтобы увидеть доступные текущему пользователю запланированные задачи:

MySQL 8.1
SELECT * FROM cron.job;

Обычный пользователь видит только собственные задачи. Суперпользователь и роль с атрибутом BYPASSRLS могут видеть задачи других пользователей.

Для просмотра истории выполнения задач:

MySQL 8.1
SELECT * FROM cron.job_run_details
ORDER BY start_time DESC
LIMIT 10;

Управление расписанием

Временное отключение события:

MySQL 8.1
ALTER EVENT cleanup_old_logs DISABLE;

Включение события:

MySQL 8.1
ALTER EVENT cleanup_old_logs ENABLE;

Изменение расписания события:

MySQL 8.1
ALTER EVENT cleanup_old_logs
ON SCHEDULE EVERY 2 HOUR;

Удаление события:

MySQL 8.1
DROP EVENT IF EXISTS cleanup_old_logs;

Удаление запланированной задачи:

MySQL 8.1
SELECT cron.unschedule('cleanup_old_logs');

Или по ID задачи:

MySQL 8.1
SELECT cron.unschedule(42);  -- где 42 - это jobid из таблицы cron.job

Изменение задачи:

Расписание, команду и активность существующей задачи можно изменить с помощью cron.alter_job():

MySQL 8.1
SELECT cron.alter_job(
    42,
    schedule := '0 */2 * * *'
);

Здесь 42jobid задачи из таблицы cron.job.

  1. Права доступа: Для создания событий нужна привилегия EVENT.

  2. Временная зона: Расписание интерпретируется в значении time_zone текущей сессии при создании или изменении события. MySQL сохраняет эту временную зону вместе с событием.

  3. Параллельные запуски: Если событие выполняется дольше своего интервала, MySQL может запустить несколько его экземпляров одновременно. При необходимости это нужно предотвращать блокировкой или другой защитой от параллельного выполнения.

  4. Производительность: Допустимая частота зависит от стоимости операции и нагрузки на базу данных, а не от универсального минимального интервала.

  1. Права доступа: Суперпользователь обычно устанавливает расширение, после чего обычным пользователям можно выдать USAGE на схему cron. Задача выполняется с правами создавшего её пользователя.

  2. Временная зона: Cron-выражения используют настройку cron.timezone, которая по умолчанию равна GMT. Текущее значение можно проверить командой SHOW cron.timezone;.

  3. Интервалы: pg_cron 1.5 и новее поддерживает интервалы от 1 до 59 секунд. Частоту нужно выбирать с учётом стоимости задачи и нагрузки.

  4. Параллельные запуски: pg_cron не запускает одновременно несколько экземпляров одной задачи. Если наступил следующий срок, пока задача ещё работает, новый запуск будет поставлен в очередь.

  5. Логирование: При включённой настройке cron.log_run сведения о запусках сохраняются в таблице cron.job_run_details. По умолчанию логирование включено.

Самопроверка

Какой минимальный интервал можно использовать для повторяющихся событий?

Какой минимальный интервал можно использовать для повторяющихся задач?

Запланированные события — это мощный инструмент для автоматизации рутинных задач в базе данных. Они помогают поддерживать чистоту данных, обновлять статистику и выполнять регламентные операции без участия разработчиков! 🚀

Запланированные задачи — это мощный инструмент для автоматизации рутинных задач в базе данных. Они помогают поддерживать чистоту данных, обновлять статистику и выполнять регламентные операции без участия разработчиков! 🚀