В Oracle существует возможность запланировать выполнение определенного набора действий в виде заданий. Задание может, представляет собой хранимую процедуру, анонимный блок PL /SQL, внешнюю процедуру на языке C или Java. Время выполнения может иметь значение любого времени суток и подчинятся заданному интервалу. Это хорошо подходит для переноса тяжёлых в обработке расчётов на менее загруженное ночное время. По умолчанию выполнение заданий выключено. Поэтому надо провести небольшую дополнительную настройку сервера.

Настройка сервера

Для того чтобы задания начались выполняться необходимо, установить параметр инициализации JOB_QUEUE_PROCESSES. Изначально он имеет значение 0 и задаёт максимальное количество фоновых процессов для выполнения заданий. В версии Oracle 9.2 максимальное значение этого параметра может составлять 1000. На практике же обычно можно ограничиться не более 5 процессами. В любом случае вы всегда можете изменить это значение с помощью команды ALTER SYSTEM SET без перезагрузки сервера. Итак, для начала внесем новую строчку в файл инициализации и перезагрузим сервер:

JOB_QUEUE_PROCESSES = 5

В файле alert.log мы увидим, что в момент, когда стартуют фоновые процессы, у нас появилось новая запись:

CJQ0 started with pid=8

Это стартовал новый фоновый процесс CJQ0, так называемый координатор заданий. Что он делает и для чего он нужен, мы рассмотрим чуть ниже, а на этом предварительную настройку сервера можно считать законченной.

Процессы

Итак, процесс координатора заданий запущен, и как можно догадаться из его названия именно этот процесс осуществляет общее управление всеми заданиями. Для начала он выбирает таблицу SYS.JOB$, в которой хранятся параметры заданий. Если среди заданий имеются те, которые будут выполняться в ближайший интервал времени указанный в скрытом параметре _JOB_QUEUE_INTERVAL (по умолчанию его значение составляет 5 секунд), то для них порождаются фоновые процессы очереди заданий Jnnn, которые в свою очередь создают сеансы для непосредственного выполнения запланированных действий. Именно максимальное количество процессов Jnnn, которые могут быть одновременно запущены и отражает настраиваемый параметр JOB_QUEUE_PROCESSES.

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

На заметку:

Выставляйте значение параметра JOB_QUEUE_PROCESSES чуть больше максимального количества одновременно запускаемых заданий. Маленькое значение может привести к сдвигу времени выполнения из-за конкуренции за процессы Jnnn. Большое значение к неоправданному запуску этих же процессов в исключительных ситуациях.

Создание заданий

Для управления заданиями в Oracle существует специальный пакет DBMS_JOB. С его помощью над ними можно осуществлять различные действия. Для начала попробуем создать новое задание. В нашем случае для этого, нужно применить следующую процедуру пакета:

DBMS_JOB.SUBMIT(
   JOB       OUT BINARY_INTEGER,
   WHAT      IN  VARCHAR2,
   NEXT_DATE IN  DATE DEFAULT SYSDATE,
   INTERVAL  IN  VARCHAR2 DEFAULT NULL,
   NO_PARSE  IN  BOOLEAN DEFAULT FALSE,
   INSTANCE  IN  BINARY_INTEGER DEFAULT any_instance,
   FORCE     IN  BOOLEAN DEFAULT FALSE);

Опишем параметры этой процедуры:

  • JOB - это идентификатор задания. Имеет уникальное значение для каждого задания, генерируемое системной последовательностью. Является выходным параметром. Его лучше запомнить, если потребуются дальнейшие действия над заданиями.

  • WHAT - тело задания. Представляет собой анонимный PL/SQL блок. Всё что здесь указано, будет выполнено в процессе работы задания. Если вы запускаете только одну процедуру, то можно не заключать её в блок достаточно поставить в конце названия процедуры точку с запятой. Значение WHAT в этом случае автоматически будет помещено в PL/SQL блок. Если процедура имеет строковые параметры, то они обязательно должны заключаться в две одинарные кавычки с каждой стороны. В PL/SQL блоке можно также писать DML и DDL команды, но нельзя производить создание и запуск заданий. Это только приведёт к ошибке ORA-32317. Если же используются ссылки на удалённую базу данных, то они должны явно включать имя и пароль. Анонимные ссылки здесь не поддерживаются. И, наконец, владельцу задания требуется явно предоставить привилегии, на объекты, используемые в теле задания.

  • NEXT_DATE - дата следующего выполнения задания. Время непосредственно задаётся владельцем или автоматически вычисляется Oracle.

    На заметку:

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

  • INTERVAL - формула интервала времени. Представляет собой DATE функцию. Именно от её правильного значения будет зависеть дата следующего выполнения задания казанного в столбце NEXT_DATE. Приведём некоторые примеры формулы интервала задания:

    Формула интервалаОписание
    NULL Задание выполнится однократно и удалится.
    TRUNC(SYSDATE+1)+n/24 Задание будет выполняться ровно в n часов каждого дня
    TRUNC(SYSDATE+1)+(n+(m/60))/24

    Задание будет выполняться ровно в n часов m минут каждого дня.

    TRUNC(LAST_DAY(SYSDATE))+(n+(m/60))/24

    Задание будет выполняться ровно в n часов m минут последнего дня каждого месяца.

    TRUNC(LAST_DAY(SYSDATE)+1)+(n+(m/60))/24

    Задание будет выполняться ровно в n часов m минут первого дня каждого месяца.


    На заметку:

    Учитывайте, что вычисление NEXT_DATE с помощью формулы интервала происходит после выполнения задания. Поэтому всегда учитывайте это время и старайтесь не ставить их на время близкое к окончанию суток из-за возможного неправильного расчёта следующей даты выполнения.

  • NO_PARSE - флаг разбора PL/SQL. Если его значение равно FALSE разбор происходит в момент установки задания. Иначе, в момент выполнения задания.

  • INSTANCE - какой экземпляр производит выполнение задания.

  • FORCE - если значение этого параметра истинно то в качестве INSTANCE может выступать любое положительное целое число. В противном случае экземпляр, указанный в INSTANCE должен быть обязательно запущен, иначе поднимается исключение.

На заметку:

При создании задания или изменения его параметров ORACLE записывает текущие параметры NLS владельца. Эти параметры каждый раз восстанавливаются при выполнении задания. Это может приводить к некоторым ошибкам в случае ожидания других значений. Поэтому если необходимо лучше производить установку нужных NLS значений с помощью команды ALTER SESSION в параметре WHAT задания.

В качестве примера создадим простое задание, которое при запуске будет делать паузу в 20 секунд, первый раз выполниться 1 января 2006 года в 1 час 5 минут и будет повторяться каждый день в то же самое время.

SQL> VARIABLE jobno NUMBER
SQL> BEGIN
  2>    DBMS_JOB.SUBMIT(:jobno,
  3>       'SYS.DBMS_LOCK.SLEEP(20);',
  4>       TO_DATE('01.01.2006 01:05','DD.MM.YYYY HH24:MI'), 'TRUNC(SYSDATE+1)+(1+(5/60))/24');
  5>    COMMIT;  -- COMMIT обязателен! 
  6> END;
 
Затрачено времени: 0.0 секунд(ы) 
 
SQL> PRINT jobno
 
Переменная:  JOBNO
Значение:    24

Изменение задания

И так задание создано. Теперь попробуем изменить некоторые его параметры. Для изменения доступны следующие параметры задания: WHAT, NEXT_DATE, INTERVAL и INSTANCE. Их можно менять все одновременно или по отдельности. К примеру, следующая процедура пакета меняет все три параметра, при этом следует учитывать, что если какой либо из них равен NULL, то значение параметра не изменится.

DBMS_JOB.CHANGE ( 
   JOB       IN  BINARY_INTEGER,
   WHAT      IN  VARCHAR2,
   NEXT_DATE IN  DATE,
   INTERVAL  IN  VARCHAR2,
   INSTANCE  IN  BINARY_INTEGER DEFAULT NULL,
   FORCE     IN  BOOLEAN DEFAULT FALSE);

А вот уже эта процедура меняет значение только параметра WHAT:

DBMS_JOB.WHAT ( 
   JOB       IN  BINARY_INTEGER,
   WHAT      IN  VARCHAR2);

Также с параметрами NEXT_DATE,INTERVAL,INSTANCE:

DBMS_JOB.NEXT_DATE ( 
   JOB       IN  BINARY_INTEGER,
   NEXT_DATE IN  DATE);

DBMS_JOB.INTERVAL ( 
   JOB       IN  BINARY_INTEGER,
   INTERVAL  IN  VARCHAR2);

DBMS_JOB.INSTANCE ( 
   JOB        IN BINARY_INTEGER,
   INSTANCE   IN BINARY_INTEGER,
   FORCE      IN BOOLEAN DEFAULT FALSE);

В качестве примера, увеличим паузу, которое делает задание, созданное ранее, до 30 секунд и изменим, время повторного запуска задания на 3 часа 15 минут:

SQL> BEGIN
  2>    DBMS_JOB.CHANGE(24,
  3>       'SYS.DBMS_LOCK.SLEEP(30);',
  4>       NULL, 'TRUNC(SYSDATE+1)+(3+(15/60))/24');
  5>    COMMIT;
  6> END;

На заметку:

Изменить параметры или совершать другие действия над заданием можно только его владельцу. В противном случае возникнет ошибка ORA-23421: job number 24 is not a job in the job queue.

Удаление задания

Если задание становиться ненужным, то его можно удалить. Сделать это можно следующей процедурой:

DBMS_JOB.REMOVE ( 
   JOB       IN  BINARY_INTEGER );

Выключение задания

Бывают случаи, когда задание временно не должно выполняться. Для этого совсем необязательно его удалять. Достаточно его просто выключить. Выключение (включение) задания производится установкой специального флага состояния - BROKEN. Делается это с помощью следующей процедуры:

DBMS_JOB.BROKEN ( 
   JOB       IN  BINARY_INTEGER,
   BROKEN    IN  BOOLEAN,
   NEXT_DATE IN  DATE DEFAULT SYSDATE);

Если флаг BROKEN имеет значение истинно, то такое задание считается разрушенным и выполняться не будет. Параметр NEXT_DATE определяет здесь дату следующего выполнения задания и действует только при его включении.

На заметку:

В момент выключения задания параметр NEXT_DATE устанавливается в максимальное значение даты. Если не указать параметр NEXT_DATE в момент включения, то задание начнёт выполняться немедленно.

Для примера выключим задание:

SQL> BEGIN
  2> DBMS_JOB.BROKEN(24, TRUE);
  3> END;

Вынужденное выполнение задания

Если необходимо запустить задание в текущий момент времени, то сделать это можно с помощью следующей процедуры пакета:

DBMS_JOB.RUN ( 
   JOB       IN  BINARY_INTEGER,
   FORCE     IN  BOOLEAN DEFAULT FALSE);

Если параметр FORCE имеет значение ложно, то задание может быть выполнено в приоритетном режиме только в указанном экземпляре, иначе ?????.

На заметку:

Задание выполняется в текущем сеансе, при этом повторно инициализируются пакеты текущего сеанса, и происходит неявная фиксация транзакции.

Экспорт задания

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

DBMS_JOB.USER_EXPORT ( 
   job    IN     BINARY_INTEGER,
   mycall IN OUT VARCHAR2);

Переменная mycall будет при этом содержать текст команды, с помощью которой можно заново создать задание.

Контроль задания

Как было указано выше, координатор заданий обращается в своей работе к системной таблице SYS.JOB$, хранящей описания всех заданий. На эту таблицу существуют три представления: DBA_JOBS, ALL_JOBS и USER_JOBS. Они наиболее часто используются для контроля над заданиями. Рассмотрим их некоторые столбцы:

  • JOB, NEXT_DATE, INTERVAL, WHAT, INSTANCE - эти столбцы знакомы нам по процедуре SUBMIT.
  • LOG_USER - пользователь под которым была создано задание. Фактически это и есть владелец задания.
  • PRIV_USER - пользователь привилегии которого используются для выполнения задания.
  • SCHEMA_USER - схема по умолчанию для разбора задания.
  • LAST_DATE (LAST_SEC) - дата (время) последнего успешного выполнения задания.
  • THIS_DATE (THIS_SEC) - дата (время) начала выполнения задания.
  • TOTAL_TIME - общее время выполнения задания в секундах. Содержит суммарное время длительности всех выполнений задания.
  • BROKEN - этот столбец показывает состояние флага разрушенного задания. Если значение равно Y задание выполняться не будет.
  • FAILURES - количество неудачных попыток выполнить задание. Максимальное значение может достигать 16.
  • NLS_ENV - NLS параметры сеанса. Соответствуют параметрам сеанса, в котором задание создавалось.

Кроме вышеперечисленных представлений существует и ещё одно - DBA_JOBS_RUNNING. Оно показывает задания, которые выполняются в текущий момент времени. С его помощью можно легко определить SID сеанса выполняемого задания.

Блокировки

Если подробнее разобрать представление DBA_JOBS_RUNNING, то можно увидеть что в его основе лежит соединение таблицы SYS.JOB$ и представления V$LOCK. Кажется, какая тут есть связь? Оказывается, есть и самая прямая. Для того чтобы гарантировать, что данное задание выполняется одновременно только в одном сеансе, Oracle выставляет блокировку JQ. Это можно хорошо видеть, сделав запрос к представлению V$LOCK во время выполнения задания:

SQL> SELECT * FROM v$lock;
 
ADDR     KADDR    SID TYPE ID1 ID2 LMODE REQUEST CTIME BLOCK
-------- -------- --- ---- --- --- ----- ------- ----- -----
67252738 67252748 12  JQ   0   24  6     0       1     0   

При этом столбец ID2 будет указывать на идентификатор выполняемого задания.

Ошибки

Когда при выполнении задания происходит ошибка, в журнал уведомлений базы данных alert.log записывается сообщение об ошибке. Оно имеет примерно следующий вид:

Wed Jan 11 21:06:12 2006
Errors in file D:\oracle\admin\ALFA9\udump\ORA00952.TRC:
ORA-12012: error on auto execute of job 24
ORA-06550: line 1, column 96:
PLS-00201: identifier 'SYS.DBMS_LOCK' must be declared
ORA-06550: line 1, column 96:
PL/SQL: Statement ignored 

Первая строка указывает дату и время ошибки. Вторая свидетельствует о том, что сообщение, об ошибке будет дополнительно сброшено в трассировочный файл ORA00952.TRC. Третья строка всегда представляет собой стандартное сообщение ошибки ORA-12012 с указанием в конце идентификатора задания. Остальные строки - это сообщения возникшего исключения. Именно они и позволяют определить причину сбоя задания. Во всяком случае, Oracle попытается снова выполнить задание через одну минуту, затем в случае неудачи через две минуты и так далее, каждый раз удваивая интервал. Как только число таких попыток станет равным 16, выставляется флаг разрушенного задания, и оно в дальнейшем не выполняется.

Прерывание задания

Если необходимо прервать выполняющееся задание, то вначале надо его выключить. Затем определить sid и serial# сеанса с помощью следующего запроса:

SQL> SELECT sid,serial#
  2> FROM v$session
  3> WHERE sid = (SELECT sid FROM dba_jobs_running WHERE job = 24)
 
SID SERIAL#
--- -------
11  50     

И только затем уничтожить сеанс командой ALTER SYSTEM KILL SESSION. Если задание предварительно не выключить, то оно снова запуститься на выполнение через минуту после уничтожения сеанса. Если попытаться продолжать уничтожать сеансы, то Oracle будет снова запускать задание, каждый раз удваивая интервал. При этом столбец FAILURES в представлении DBA_JOBS остаётся нулевым, и вы не уведите в журнале уведомлений базы данных alert.log сообщений, что задание было прервано.