Inhaltsverzeichnis

Der Oracle Job Scheduler 10g/11g/12c/18c/19c

Erste Version 08/2018 - 10g, 11g, 12c, 18c, 19c

Der Oracle Jobs Scheduler in der Datenbank ist sehr mächtig und kann sehr umfangreich konfiguriert werden.

Mit dem Scheduler lassen sich komplexe Job Ketten aufbauen und komplexe Zeitpläne realisieren, das Filesystem überwachen etc.

Leider ist daher eine einfache Definition eines Standard Job im Umkehrschluss oft etwas kompliziert, da viele Parameter definiert werden können und/oder müssen.

Verstärkend kommt hinzu, das einzelne Elemente bei der Definition eines Jobs wiederum optional sind und mehrere Weg damit zum gleichen Ziel führen.

Siehe auch:


Übersicht über die Abhängigkeiten bei einer Definition eines Jobs:

 Übersicht über die Elemente der Oracle Job Steuerung per DBMS_SCHEDULER

Die einzelnen Elemente eines Jobs:

Globale Parameter für die Jobs

Die aktuelle Zeitzone für den Job

Zeitzone überwachen:

SELECT DBMS_SCHEDULER.STIME FROM DUAL;
 
STIME
----------------------------------------
10.12.14 19:28:51,178000000 +01:00

Zeitzone setzen:

BEGIN
  DBMS_SCHEDULER.set_scheduler_attribute(
     attribute => 'default_timezone'
   , VALUE     => 'EUROPE/BERLIN');
END;
/
 
SELECT DBMS_SCHEDULER.STIME FROM DUAL;
 
STIME
------------------------------------------------------
10.12.14 19:31:04,039000000 EUROPE/BERLIN

Jobs überwachen

Für das Überwachen kann dieses Script hilfreich sein: http://orapowershell.codeplex.com/SourceControl/latest#sql/jobs_sheduler.sql

Übersicht über die DBA Views bgzl. dem DBMS Scheduler:

Übersicht der DB Views um Scheduler Jobs zu überwachen

Monitoring aktiveren

Setzen über den die JOB Klasse
BEGIN
  DBMS_SCHEDULER.SET_ATTRIBUTE (
   'DEFAULT_JOB_CLASS', 'logging_level', DBMS_SCHEDULER.LOGGING_FAILED_RUNS);
END;

Job Logs automatisch lösen

Global:

BEGIN
DBMS_SCHEDULER.SET_SCHEDULER_ATTRIBUTE('log_history','90');
END;
/

Auf einer Job Klasse:

BEGIN
DBMS_SCHEDULER.SET_ATTRIBUTE('DEFAULT_JOB_CLASS','log_history','130');
END;

Job Logs per Hand löschen

Alles:

BEGIN
DBMS_SCHEDULER.PURGE_LOG();
END;
/

Nur alles was älter ist als 10 Tage und nur vom JOB LOG:

EXEC DBMS_SCHEDULER.PURGE_LOG(log_history => 10, which_log => 'JOB_LOG');

Fein Granuar:

EXEC DBMS_SCHEDULER.PURGE_LOG(log_history => 10, job_name => 'CLEAN_SQL_ERROR_LOG_TABLE, DEFAULT_JOB_CLASS');                      

Einen Job ohne weitere Elemente (alles "inline") anlegen

Einfachste Methode um einen Scheduler Job anzulegen:

BEGIN
   DBMS_SCHEDULER.create_job (
      job_name        => 'CLEAN_SQL_ERROR_LOG_TABLE'
    , job_type        => 'PLSQL_BLOCK'
    , job_action      => 'BEGIN system.deleteOraErrorTrigTab(15); END;'
    , start_date      => SYSTIMESTAMP
    , repeat_interval => 'freq=daily; byhour=13; byminute=0'
    , end_date        => NULL
    , enabled         => TRUE
    , comments        => 'Job to clean all lean Error Log older then xx days');
END;
/

Einen Lightwight Job ohne weitere Elemente anlegen

Ab 11g R1 kann ein Job „Lightweight“ angelegt werden, der Vorteil ist das weniger im Data Dictionary an Objekten angelegt werden muss.

Das erleichert das Anlegen, wenn viele einmalige Jobs angelegt werden müssen (Parameter job_style ⇒ 'LIGHTWEIGHT' !).

Allerdings muss dazu zuvor ein „Program“ definiert werden.

BEGIN
  DBMS_SCHEDULER.create_program (
      program_name        => 'CLEAN_SQL_ERROR_LOG_TABLE_PROG'
    , program_type        => 'STORED_PROCEDURE'
    , program_action      => 'system.deleteOraErrorTrigTab'
    , number_of_arguments => 1
    , enabled             => FALSE
    , comments            => 'Prog to clean Error Log older then xx days');
END;
/
 
BEGIN
  DBMS_SCHEDULER.define_program_argument (
      program_name      => 'CLEAN_SQL_ERROR_LOG_TABLE_PROG'
    , argument_name     => 'p_keepdays'
    , argument_position => 1
    , argument_type     => 'NUMBER'
    , default_value     => '15');
END;
/
BEGIN
  DBMS_SCHEDULER.enable (name => 'CLEAN_SQL_ERROR_LOG_TABLE_PROG');
END;
/
 
BEGIN
  DBMS_SCHEDULER.create_job (
     job_name         => 'CLEAN_SQL_ERRTABLE_LIGHT'
    , program_name    => 'CLEAN_SQL_ERROR_LOG_TABLE_PROG'
            , job_style       => 'LIGHTWEIGHT'
    , comments        =>  'Job to clean all lean Error Log older then xx days'
            , enabled         => true);
END;
/
 
-- nur im Job Log steht ein Eintrag!
 
select * 
  from dba_scheduler_job_log
where job_name = 'CLEAN_SQL_ERRTABLE_LIGHT'
;

Einen Job mit "Program" und "Schedule" anlegen

-- programm von vorherigen Beispiel Lightwight Job wird weiterverwendet
 
select *  
  from dba_scheduler_programs 
 where program_name = 'CLEAN_SQL_ERROR_LOG_TABLE_PROG'
/ 
 
 
-------------------------------------------------------------------------
-- Create Oracle Scheduler Time Plan
BEGIN
  DBMS_SCHEDULER.create_schedule (
     schedule_name   => 'DBA_TIMEPLAN'
    , start_date      => SYSTIMESTAMP
    , repeat_interval => 'freq=daily; byhour=13; byminute=0'
    , end_date        => NULL
    , comments        => 'Job time plan for DB Maintenance');
END;
/
 
select * 
  from dba_scheduler_schedules 
 where schedule_name = 'DBA_TIMEPLAN'
/ 
 
 
-------------------------------------------------------------------------
-- Create Scheduler Job
BEGIN
  DBMS_SCHEDULER.create_job (
     job_name         => 'CLEAN_SQL_ERROR_LOG_TABLE'
    , program_name    => 'CLEAN_SQL_ERROR_LOG_TABLE_PROG'
    , schedule_name   => 'DBA_TIMEPLAN'   
    , comments        =>  'Job to clean all lean Error Log older then xx days'
    , enabled         => false);
 
  DBMS_SCHEDULER.set_job_argument_value( job_name => 'CLEAN_SQL_ERROR_LOG_TABLE'
	                               , argument_name => 'p_keepdays'												
                                       , argument_value => '10'
	);         
 
 
  DBMS_SCHEDULER.enable (name => 'CLEAN_SQL_ERROR_LOG_TABLE');
 
END;
/ 
 
select * from dba_scheduler_jobs 
where job_name = 'CLEAN_SQL_ERROR_LOG_TABLE'
/

Alles wieder entfernen:

begin
  DBMS_SCHEDULER.drop_job      (job_name       => 'CLEAN_SQL_ERROR_LOG_TABLE');
  DBMS_SCHEDULER.drop_schedule (schedule_name  => 'DBA_TIMEPLAN');
  DBMS_SCHEDULER.drop_program  (program_name   => 'CLEAN_SQL_ERROR_LOG_TABLE_PROG');
end;
/  

RAC - Instance Stickiness - Job auf eine Instance binden

Lösung A) Über eine Job Klasse und einen Service
Lösung B) - Schalter Instance Stickiness => True

Wenn „Instance Stickiness“ auf „TRUE“ dann versucht die DB den Job auf der Instance wieder zu starten, auf der zu letzt der Job lief, ABER nur wenn diese Instance auch die Instance mit dem aktuelle geringsten Last ist! D.h. es kann sich in einem System mit stark wechselnder Last nicht darauf verlassen werden das der Job wirklich jedesmal auf der selben Instance startet!

Lösung C) Job auf eine Instance binden
# stetzen
begin  
  dbms_scheduler.set_attribute( name => 'GPI.DO_MY_JOB' 
                               , attribute=>'INSTANCE_ID'
                               , value=> 3
  );  
end;  
/ 
 
#prüfen mit:
 
set serveroutput on
 
declare
 v_out varchar2(255);
begin
  dbms_scheduler.get_attribute( name     => 'GPI.DO_MY_JOB' 
                              , attribute=>'INSTANCE_ID'
                              , value=> v_out
   );  
 
  dbms_output.put_line('-- Info   INSTANCE_ID Attribute::'||v_out);
end; 
/ 
 
#wieder auf null stetzen:
 
begin
  dbms_scheduler.set_attribute_null ( name       => 'GPI.DO_MY_JOB' 
                                   ,attribute  => 'INSTANCE_ID'
  );
end;
/

Scheduler Calendaring Syntax

Bei DBMS_SCHEDULER.create_schedule kann über ein Zeitintervall angegeben werden.

Die Syntax ist recht komplex ⇒http://docs.oracle.com/cd/E11882_01/appdev.112/e40758/d_sched.htm#ARPLS72273 ( nach repeat_interval suchen), für 12c siehe https://docs.oracle.com/en/database/oracle/oracle-database/12.2/admin/scheduling-jobs-with-oracle-scheduler.html#GUID-10B1E444-8330-4EC9-85F8-9428D749F7D5

Beispiele:

RegelBedeutung
FREQ=hourly;BYMINUTE=0stündlich aufrufen
FREQ=MINUTELY; INTERVAL=15alle 15 Minuten aufrufen
FREQ=WEEKLY; BYDAY=MONJeden Montag aufrufen
FREQ=WEEKLY; BYDAY=Mon;BYHOUR=2;BYMINUTE=30;BYSECOND=0 Jeden Montag um 2:30:00 aufrufen

Der Calender Ausdruck kann mit DBMS_SCHEDULER.EVALUATE_CALENDAR_STRING getestet werden.

DECLARE	
	v_next_run_date TIMESTAMP;
	v_start_date    TIMESTAMP:=systimestamp;  
        v_return_date_after TIMESTAMP
BEGIN
  FOR i IN 1 ..10
  loop
  DBMS_SCHEDULER.EVALUATE_CALENDAR_STRING(  
		  calendar_string   => 'FREQ=MINUTELY; INTERVAL=15'
		, start_date        => v_start_date
		, return_date_after => v_return_date_after 
		, next_run_date     => v_next_run_date);
 
	DBMS_OUTPUT.PUT_LINE('-- Info actual date :: '||to_char(v_start_date,'dd.mm.yyyy hh24:mi')||
	                     '   --> next_run_date:: '||to_char(v_next_run_date,'dd.mm.yyyy hh24:mi')
	);
         v_return_date_after := v_next_run_date;
  END loop;
END;
/
 
-- Info actual date :: 16.01.2015 19:19   --> next_run_date:: 16.01.2015 19:34
 
-- als Funktion
 
CREATE OR REPLACE FUNCTION getNextRunDate(p_calendar_string      varchar2
                                         ,p_start_date           TIMESTAMP WITH TIME ZONE DEFAULT systimestamp
                                         ,p_return_date_after    TIMESTAMP WITH TIME ZONE DEFAULT systimestamp)
   RETURN DATE
IS
   v_next_run_date   TIMESTAMP WITH TIME ZONE;
BEGIN
   dbms_scheduler.EVALUATE_CALENDAR_STRING(calendar_string     => p_calendar_string
                                          ,start_date          => p_start_date
                                          ,return_date_after   => p_return_date_after
                                          ,next_run_date       => v_next_run_date);
 
   RETURN v_next_run_date;
END;
/
 
-- abfragen über alle Jobs wann die Jobs das nächste mal und das übernächste mal laufen würden:
 
SELECT js.job_name
      , JS.REPEAT_INTERVAL
      , js.next_run_date
      , getNextRunDate(JS.REPEAT_INTERVAL,js.next_run_date,js.next_run_date) AS over_next_run_date
      , js.state||' - '||js.job_action  AS job_info     
    FROM dba_scheduler_jobs js
       , dba_objects o
   WHERE     js.owner = o.owner(+)
         AND js.job_name = o.OBJECT_NAME(+)
         AND js.owner != 'SYS'
/         

Nachträglich ändern:

BEGIN
DBMS_SCHEDULER.SET_ATTRIBUTE (
     name       => 'CHECK_LONG_RUNNING_TIMEPLAN'
   , attribute  => 'repeat_interval'
   , VALUE      =>  'FREQ=MINUTELY; INTERVAL=15');
END;
/
 
 
SELECT * 
  FROM dba_scheduler_schedules 
 WHERE schedule_name = 'DBA_TIMEPLAN'
/ 

Window Funktionalität nützen

Ein Scheduler Window legt den aktiven Resource Plan für den Zeitraum, in dem das Fenster „offen“ ist, fest.

D.h. ein per init.ora definiert globaler Resourceplan wird von diesem Scheduler Window überschrieben!

Vorteil:

Nachteil:

siehe auch ⇒ Den Oracle Resource Manager ab 11g verwenden

Über den Ressource Plan im Windows und der Consumer Group + Service Angabe in der Job Class wird dann der Job auf das Window gebunden und entsprechend in der Gruppe des Resource Plans priorisiert.

Übersicht:

 Oracle Scheduler Window and Resource Manager

Siehe Statement dazu im Orginal Doku ⇒ Job Classes

Das der Scheduler den Plan gesetzt hat, kann am init.ora Parameter „resource_manager_plan“ erkannt werden, hier steht dann ein „SCHEDULER[0x196257]:GPI_LOW_LOAD“ im aktuellen Wert!

Die Bedeutung des 0x196257 ist leider nuklar, gibt kein DB Objekt mit der ID, und die ID in den Log Tabellen ist es auch nicht. Am suchen…

Überwachen mit:

Übersicht über die beteiligten Views:

 DB Views Window Funktionalität

Zu welchen Plan ist ein Job Fenster zugeordnet:

COLUMN job_class_name    format a25
COLUMN job_class_service format a25
COLUMN resource_plan     format a28
COLUMN consumer_group    format a25
COLUMN job_window        format a20
 
 
SELECT jc.job_class_name
     , jc.service AS job_class_service 
     , cg.consumer_group
     , pd.plan     AS resource_plan
     , sw.window_name AS job_window
  FROM dba_scheduler_job_classes jc  
      , dba_rsrc_consumer_groups cg
      , dba_rsrc_plan_directives pd
      , dba_scheduler_windows sw
      , DBA_RSRC_GROUP_MAPPINGS gm
  WHERE jc.resource_consumer_group = cg.consumer_group                    
    AND cg.consumer_group=pd.group_or_subplan (+) 
    AND sw.resource_plan(+)=pd.group_or_subplan 
 ORDER BY 1
/

Probleme

siehe auch Datenbank Auto Statistik Job überwachen - Fehlerursache bei veralteten Statistiken

ORA-27486: Nicht ausreichende Berechtigungen

Problem:

ORA-27486: Nicht ausreichende Berechtigungen
ORA-06512: IN "SYS.DBMS_ISCHED", Zeile 168
ORA-06512: IN "SYS.DBMS_SCHEDULER", Zeile 288
ORA-06512: IN Zeile 2

Lösung:

GRANT CREATE job TO <SCHEMA>;

ORA-27476: "%" does not exist Check the log/trace file for more Details

Eine Job Klasse muss den entsprechenden User „gegranted“ werden!

sys.dbms_scheduler.create_job( 
...
job_class => 'MY_ADMIN_CLASS'
...
 
...
 
ORA-27476: "MY_ADMIN_CLASS" does NOT exist CHECK the log/trace file FOR more Details
 
 
GRANT EXECUTE  ON SYS.MY_ADMIN_CLASS TO GPI;

Einen Job wieder entfernen

Über den <Schema Owner>.<Job Name> kann mit der Routine dbms_scheduler.drop_job ein Job entfernt werden, mit force⇒true wird der Job sofort gestoppt und entfernt, falls false erst nach dem nächsten Lauf.

Beispiel:

BEGIN
 dbms_scheduler.drop_job (job_name => 'BGJOBUSER.SYMON', force=>TRUE);
END;
/

Auswertung des Job Logs sehr langsam

Evlt. sind es einfach zuviele Einträge im Log, siehe ⇒ How To Purge DBA_SCHEDULER_JOB_LOG and DBA_SCHEDULER_WINDOW_LOG (Doc ID 443364.1)

Hierzu auch folgende Anmerkungen beachten ⇒ DBMS_SCHEDULER Job Log Lösch-Verhalten - Löschen optimieren


Job Überwachen

Hier ein SQL um alle Jobs mit Ihren Laufzeiten und den letzten Fehler aus den Log zu überwachen:

WITH jobs AS (
SELECT  js.owner
       ,  js.job_name
       ,  decode (js.state, 'SHUD', 'SCHEDULED', 'DIS' , 'DISABLED', 'RUN', 'RUNNING',   js.state) AS state
       ,  js.run_count
       ,  js.failure_count
       ,  to_char (js.last_start_date, 'dd.mm hh24:mi') AS last_start_date
       ,  to_char (js.next_run_date, 'dd.mm hh24:mi')   AS next_run_date
       , LAST_RUN_DURATION
    FROM dba_scheduler_jobs js
)
, 
last_log_date AS (
SELECT owner
    , job_name 
    , MAX(log_date) AS log_date
 FROM dba_scheduler_job_log 
GROUP BY  owner
    , job_name
    )
, last_logs AS (
  SELECT l.* 
    FROM dba_scheduler_job_log l  
INNER JOIN last_log_date ld 
  ON (    ld.owner=l.owner 
      AND  ld.job_name=l.job_name
      AND ld.log_date=l.log_date
    )
 
)
 
SELECT j.OWNER
    , j.JOB_NAME
    , j.STATE
    , j.RUN_COUNT
    , j.FAILURE_COUNT
    , round( EXTRACT( SECOND FROM j.LAST_RUN_DURATION ) 
          + EXTRACT( MINUTE FROM j.LAST_RUN_DURATION ) * 60 
          + EXTRACT( HOUR FROM j.LAST_RUN_DURATION ) * 60 * 60 
          + EXTRACT( DAY FROM j.LAST_RUN_DURATION ) * 60 * 60 * 24
            ,2) AS LAST_RUN_DURATION_SEC
    , j.LAST_START_DATE
    , j.NEXT_RUN_DATE
    , to_char(l.LOG_DATE, 'dd.mm hh24:mi') AS last_log_date
    , nvl(l.STATUS,'NO_LOG_FOR_THIS_JOB') AS  last_log_status
  FROM jobs j 
 LEFT JOIN last_logs l
   ON  (j.owner=l.owner AND  j.job_name=l.job_name)
WHERE 1=1
 -- only active  Jobs
 AND state != 'DISABLED'
ORDER BY j.owner,j.job_name
;

Quellen zum Thema

Oracle:

Netz:

Bei 19c zu beachten ⇒ https://mikedietrichde.com/2020/05/21/dbms_job-one-off-patch-needed-for-oracle-19-3-0-19-7-0/