Довольно часто возникает потребность выполнения определенных плановых работ в БД (сбор статистики, очистка логов). У пользователей БД тоже могут быть задачи, требующие периодического выполнения, например закрытие периода, подготовка отчетов, передача данных в другие системы и т.д. В СУБД Oracle для этого есть планировщик заданий. Но несмотря на то, что планировщик заданий в Oracle – довольно мощное средство, остается актуальным и использование различных планировщиков задач ОС (cron, at и др.).
Сразу хочу отметить, что вариант перехода от cron к планировщику Oracle здесь не рассматривается, т.к. в моем случае это было невозможно в силу многих причин (и не только технических).
Время работы приложения зависит от многих факторов: количество данных в таблицах, загрузка ресурсов БД и многих других. И может сложиться, и складывается ситуация, когда время выполнения задачи становится больше, чем период ее запуска. И если планировщик Oracle такое развитие событий отслеживает и предотвращает повторный старт до окончания предыдущей сессии, то cron запускает новый процесс вне зависимости от того, завершились предыдущие запуски или нет.
Работа одного и того же приложения одновременно в нескольких сессиях ни к чему хорошему привести не может, в лучшем случае – это просто лишняя нагрузка на сервер, в худшем – взаимные блокировки (deadlock) и как следствие – аварийное завершение приложений и/или значительное увеличение времени работы из-за многочисленных откатов транзакций.
Как один из вариантов предотвращения параллельного запуска предлагается блокировка на уровне приложения. Такой способ устраняет и еще одну проблему – когда несколько пользователей пытаются одновременно запустить одно и то же приложение вручную (не через cron).
Для решения поставленной задачи будем использовать представление gv$session, сохраняя дополнительную информацию о выполняющейся в текущий момент сессии приложения в поле module. При старте приложения будем записывать в него имя приложения (для каждого приложения оно уникально), предварительно проверив, нет ли уже выполняющихся сессий с таким же значением поля module.
Ниже приведен пример реализации данного подхода:
Create function set_lock(pi_event_name in varchar2 ,po_error_message out varchar2) return boolean is l_count number; l_session_id varchar2(256); begin -- get current session id l_session_id := sys_context('USERENV', 'SESSIONID'); select count(*) into l_count from gv$session where module = pi_event_name and audsid != l_session_id; if l_count > 0 then po_error_message := 'Other session found'; return FALSE; end if; DBMS_APPLICATION_INFO.set_module(pi_event_name, NULL); po_error_message := NULL; return TRUE; end set_lock;
В код PL/SQL приложения добавляется вызов этой функции:
if set_lock(l_event_name, l_error_message) = FALSE then return; end if;
Таким образом, используя только представление gv$session, не добавляя новых таблиц и не меняя структуру существующих, мы получили достаточно эффективное средство предотвращения параллельного запуска. Это решение работает не только с планировщиками задач, но и делает невозможным запуск одной и той же задачи разными пользователями одновременно. Предлагаемое решение достаточно простое, скорее всего я не «открыл Америку», но может быть кому-то этот вариант пригодится в работе.
ссылка на оригинал статьи https://habr.com/ru/articles/800295/
Добавить комментарий