Freitag, März 30, 2012

dbms_scheduler und die Sommerzeit

Dieser Tage ist mir in einem Kundensystem folgendes Phänomen begegnet: mit Beginn der Sommerzeit verschoben sich einige - aber nicht alle - Scheduler-Jobs um genau eine Stunde nach hinten. Ganz offenbar hatte jemand vergessen, die Uhr umzustellen - aber wer? Und warum? Ein Blick in dba_scheduler_jobs zeigte, dass der Unterschied zwischen den Jobs, die die Sommerzeit korrekt behandelt hatten, und jenen, denen das nicht gelungen war, bei den Zeitangaben klar zu erkennen war:
  • die korrekte Zeit lieferten die Jobs mit Zeitzonenangabe, also z.B.: EUROPE/BERLIN
  • die falsche Zeit lieferten die Jobs mit Offset, also +01:00
Was mir durchaus einleuchtet. Der feste Offset kann natürlich nur entweder zur Sommer- oder zur Winterzeit passen. Und wahrscheinlich waren alle Jobs, bei denen sich Verschiebungen ergeben hatten, erst nach dem Ende der letzten Sommerzeit definiert worden (oder niemand hatte die Verschiebung vorher bemerkt).

Aber was führte zur unterschiedlichen Definition der Jobs? Nach einigem Ausprobieren (und Recherchieren) wurde klar, dass das start_date für die Zuordnung der Zeitzone verantwortlich ist. Die Dokumentation (11.2) sagt dazu: "The Scheduler retrieves the date and time from the job or schedule start date and incorporates them as defaults into the repeat_interval." Dazu ein Beispiel:

exec dbms_scheduler.drop_job (job_name => 'test_mpr_job');

begin
  dbms_scheduler.create_job (
    job_name        => 'test_mpr_job',
    job_type        => 'plsql_block',
    job_action      => 'begin null; end;',
    start_date      => systimestamp,
    repeat_interval => 'freq=hourly; byminute=42',
    enabled         => TRUE,
    comments        => 'Test Scheduler Start_Date.');
end;
/

select systimestamp from dual;

SYSTIMESTAMP
-------------------------------
30.03.12 10:42:15,296000 +02:00

select job_name
     , start_date
  from dba_scheduler_jobs
 where job_name like 'TEST_MPR_JOB';

JOB_NAME     START_DATE
------------ -------------------------------
TEST_MPR_JOB 30.03.12 10:42:15,265000 +02:00

-- abgerufen nach dem Lauf
select log_date
     , req_start_date
     , actual_start_date
  from dba_scheduler_job_run_details
 where job_name = 'TEST_MPR_JOB';

LOG_DATE                          REQ_START_DATE                    ACTUAL_START_DATE
--------------------------------- --------------------------------- -------------------------------
30.03.12 10:42:15,750000 +02:00   30.03.12 10:42:15,300000 +02:00   30.03.12 10:42:15,750000 +02:00

Offenbar wird der systimestamp in diesem Fall als start_date übernommen, wobei die granulareren Zeitangaben - in diesem Fall also die Angaben unterhalb der Minutenangabe - in den Zeitplan eingehen, so dass die Ausführung nicht auf 42:00,000000, sondern auf 42:15,300000 festgelegt wird (REQ_START_DATE). Der tatsächliche Start (ACTUAL_START_DATE) verzögerte sich im gegebenen Fall minimal und entspricht dann auch dem LOG_DATE.

Interessant ist allerdings, was passiert, wenn man auf die start_date-Angabe komplett verzichtet:

begin
  dbms_scheduler.create_job (
    job_name        => 'test_mpr_job',
    job_type        => 'plsql_block',
    job_action      => 'begin null; end;',
    repeat_interval => 'freq=hourly; byminute=42',
    enabled         => TRUE,
    comments        => 'Test Scheduler Start_Date.');
end;
/

select job_name
     , start_date
  from dba_scheduler_jobs
 where job_name like 'TEST_MPR_JOB'
;

JOB_NAME     START_DATE
------------ --------------------------------------
TEST_MPR_JOB 30.03.12 10:55:12,603528 EUROPE/VIENNA

Wien kam dabei etwas unerwartet, aber die Dokumentation hat dazu folgende Erklärung:
When start_date is NULL, the Scheduler determines the time zone for the repeat interval as follows:
1. It checks whether or not the session time zone is a region name. The session time zone can be set by either:
  • Issuing an ALTER SESSION statement, for example: SQL> ALTER SESSION SET time_zone = 'Asia/Shanghai';
  • Setting the ORA_SDTZ environment variable.
2. If the session time zone is an absolute offset instead of a region name, the Scheduler uses the value of the DEFAULT_TIMEZONE Scheduler attribute. For more information, see the SET_SCHEDULER_ATTRIBUTE Procedure.
3. If the DEFAULT_TIMEZONE attribute is NULL, the Scheduler uses the time zone of systimestamp when the job or window is enabled.

Im gegebenen Fall ist Punkt 2 relevant: die DEFAULT_TIMEZONE für den Scheduler wurde explizit gesetzt:

select dbms_scheduler.stime
  from dual;

STIME
-----------------------------------------
30.03.12 11:00:07,645730000 EUROPE/VIENNA

Wahrscheinlich wurden also die Jobs, die die Sommerzeit korrekt umsetzten, ohne start_date-Angabe angelegt und konnten deshalb den Scheduler-Default verwenden. Alternativ könnte man das start_date auch explizit auf eine Angabe mit Zeitzone setzen:

begin
  dbms_scheduler.create_job (
    job_name        => 'test_mpr_job',
    job_type        => 'plsql_block',
    job_action      => 'begin null; end;',
    start_date      => systimestamp at time zone 'EUROPE/BERLIN',
    repeat_interval => 'freq=hourly; byminute=55',
    enabled         => TRUE,
    comments        => 'Test Scheduler Start_Date.');   
end;
/    

Die einfachste Möglichkeit zur Korrektur der fehlerhaften Angaben war dann eine explizite Änderung der start_date-Angabe mit Hilfe der set_attribute-Prozedur:

begin
    DBMS_SCHEDULER.SET_ATTRIBUTE(
        name => 'test_mpr_job'
      , attribute => 'start_date'
      , value => '28.03.12 11:00:00,000000 EUROPE/BERLIN');

Keine Kommentare:

Kommentar veröffentlichen