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:
Die einzelnen Elemente eines Jobs:
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
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:
BEGIN DBMS_SCHEDULER.SET_ATTRIBUTE ( 'DEFAULT_JOB_CLASS', 'logging_level', DBMS_SCHEDULER.LOGGING_FAILED_RUNS); END;
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;
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');
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; /
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' ;
-- 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; /
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!
# 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; /
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:
Regel | Bedeutung |
---|---|
FREQ=hourly;BYMINUTE=0 | stündlich aufrufen |
FREQ=MINUTELY; INTERVAL=15 | alle 15 Minuten aufrufen |
FREQ=WEEKLY; BYDAY=MON | Jeden 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' /
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:
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…
Übersicht über die beteiligten Views:
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 /
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>;
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;
Ü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; /
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
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 ;
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/