DBMS_SCHEDULER und die Zeitumstellung

Alle (halbe) Jahre wieder kommt die Umstellung zwischen Winterzeit und Sommerzeit. Daher stelle ich hier einen Erfahrungsbericht eines konkreten Troubleshooting-Falles ein, weil sich dabei ein paar Details zum Verhalten des Datenbank-Schedulers rund um die Zeitumstellung lernen lassen.

Anlass war ein Problem eines Kunden, bei dem nach Umstellung auf die Sommerzeit ein Job plötzlich nicht mehr zum richtigen Zeitpunkt ausgeführt wurde.
Bemerkt wurde das, weil auf einem anderen System die Abholung von Dateien, die besagter Job erzeugt, eine halbe Stunde nach dem geplanten Start dieses Jobs ausgeführt wird. Und dieser Abhol-Job hatte nun, nach der Zeitumstellung, keine Dateien vorgefunden.

So sieht das Run Log der letzten Tage des betroffenen Jobs aus:

SELECT log_date, req_start_date, actual_start_date
  FROM SYS.DBA_SCHEDULER_JOB_RUN_DETAILS
 WHERE job_name LIKE 'PROJENA_SDG_OHNE_AUSL'
 ORDER BY 1;
 
LOG_DATE                            REQ_START_DATE                      ACTUAL_START_DATE                  
----------------------------------- ----------------------------------- -----------------------------------
28/03/2015 01:00:00.661285 +01:00   28/03/2015 01:00:00.000000 +01:00   28/03/2015 01:00:00.138587 +01:00  
29/03/2015 01:00:01.449218 +01:00   29/03/2015 01:00:00.200000 +01:00   29/03/2015 01:00:01.271072 +01:00  
30/03/2015 02:02:11.457073 +02:00   30/03/2015 01:00:00.300000 +01:00   30/03/2015 01:00:00.799840 +01:00
31/03/2015 02:00:30.592837 +02:00   31/03/2015 01:00:00.800000 +01:00   31/03/2015 01:00:03.406370 +01:00

Man beachte den Sprung in der Zeitzone des Log_Date von „+1“ auf „+2“, den aber das Start_Date nicht mitmacht!

Dann schauen wir doch mal nach dem Start_Date und dem Next_Date:

SELECT start_date, repeat_interval
  FROM sys.dba_scheduler_jobs
 WHERE job_name LIKE 'MEIN_JOB';

START_DATE                          REPEAT_INTERVAL
----------------------------------- --------------------------------------------------------------------------------
17/11/2011 14:14:15.533578 +01:00   FREQ=DAILY; BYDAY=MON,TUE,WED,THU,FRI,SAT,SUN; BYHOUR=1; BYMINUTE=0;BYSECOND=0
 

Das sieht doch eigentlich erst einmal nicht so schlecht aus: Der Job wurde irgendwann einmal angelegt und bekam dabei sein start_date. Ansonsten soll er jeden Tag um 01:00 Uhr früh laufen. Warum bloß macht er das aber nicht? Und warum geht es bei anderen Jobs?

Die Recherche dauerte eine Weile, ich kürze mal ab und nenne ein paar geprüfte Punkte:

  1. Ist das Time Zone File aktuell? Das war es nicht ganz (v14), aber das war nicht die Ursache.
  2. Sind die NLS-Parameter bei den „guten“ und „schlechten“ Jobs verschieden? (waren sie nicht)
  3. Ist die Zeitzone auf dem DB-Server seitens des Betriebssystems richtig gesetzt? (war sie)
  4. Ist die Zeitzone für den Listener richtig gesetzt? Bei Grid Infrastructure muss das im Grid-Environment geprüft werden. (war korrekt eingestellt)
  5. Gibt es sonst noch Unterschiede zwischen den „guten“ und „schlechten“ Jobs?

Zunächst schien es so, dass die richtig laufenden Jobs alle mit Zeitzone „+02:00“ liefen, die anderen mit „+01:00“. Das hatte sich bei näherem Hinsehen aber als inkonsistent erwiesen. Dabei fiel im TOAD dann aber eine Sache auf:

Im Übersichtsfenster (wie in der Abfrage oben) wurde bei manchen Jobs ein Offset („+01:00“) angegeben, im „Alter Job“-Dialog aber die Zeitzone „Europe/Vienna“. Bei manch anderen (den „schlechten“) war aber in beiden Feldern ein Offset. Also nochmal auf dem Fußweg den betroffenen Job gecheckt:

col repeat_interval for a80
SELECT start_date, to_char(start_date, 'tzr') tz, repeat_interval
  FROM sys.dba_scheduler_jobs
 WHERE job_name LIKE 'MEIN_JOB';

START_DATE                          TZ
----------------------------------- --------------------------------
17/11/2011 14:14:15.533578 +01:00   +01:00

Es hängt also tatsächlich mit der Zeitzoneninfomation im Start_Date zusammen!
Das war ein guter Ausgangspunkt, um mit brauchbaren Stichworten bei MOS nachzusehen, und dort findet sich dann in Doc ID 467722.1:

If it returns a offset (ex: -08:00) then the job time is defined with a offset. Timezone offsets are by nature NOT „DST aware“ seen they mean a fixed offset from UTC. So any job using a offset as timezone will run after a DST change at a wrong time.

Kurz übersetzt also: Enthält das Start_Date nur ein Offset und keine benannte Zeitzone, dann stellt sich der Job auch nicht auf die Sommer-/Winterzeit ein.

Warum sich da ein Offset statt einer Zeitzone eingeschlichen hatte, war im Nachhinein nicht mehr eindeutig zu klären. Aber da die Job-Definition schon ein paar Jahre alt war und die DB mittlerweile mehrfach migriert wurde, liegt die Ursache wahrscheinlich in einer Zeit, in der es für den Scheduler noch keine „Default Time Zone“ gab. Die gibt es nämlich erst ab Oracle 10.2.0.4 (vorher ggf. mit Patch).
Beim Einrichten des Jobs wird nämlich die Zeitzone der Client-Session verwendet, und die ist per Default ein Offset! Siehe ebenfalls Doc ID 467722.1:

In oracle the session timezone (select sessiontimezone from dual;) defaults to a offset ( like +05:00), even if the Operating system „TZ“ variable is set to a named TZ, unless the ORA_SDTZ is set in the client (!) environment (or registry) with a *oracle* TZ name.

Ob und wie die Default Time Zone gesetzt ist, lässt sich folgendermaßen prüfen (und mit „SET“ statt „GET“ setzen):

-- Alternativ:
-- select dbms_scheduler.stime from dual;
DECLARE
  tz  VARCHAR2 (100);
BEGIN
  DBMS_SCHEDULER.GET_SCHEDULER_ATTRIBUTE ('default_timezone', tz);
  DBMS_OUTPUT.put_line ('Bisherige Zeitzone: '||tz);
END;
/

Bisherige Zeitzone: Europe/Vienna

Lösung

Die Lösung lautet hier: Abändern des START_DATE, so dass es die korrekte Zeitzone anstelle eines Offsets enthält. Das geht am leichtesten, indem das START_DATE einfach zurückgesetzt wird. Es enthält danach den aktuellen Zeitstempel mit Zeitzone, ohne dass der Job tatsächlich gestartet wird.

BEGIN
      SYS.DBMS_SCHEDULER.DISABLE
        (name       => 'MEIN_JOB');
      SYS.DBMS_SCHEDULER.SET_ATTRIBUTE_NULL
        ( name      => 'MEIN_JOB'
         ,attribute => 'START_DATE' );
      SYS.DBMS_SCHEDULER.ENABLE
        (name       => 'MEIN_JOB');
END;
/

Wer sich ganz sicher sein will oder Oracle < 10.2.0.4 einsetzt, kann das START_DATE natürlich auch auf einen Timestamp mit explizit angegebener Zeitzone setzen.

Weblinks

Schreibe einen Kommentar

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s