=====PL/SQL - Eine DB Session "schlafen" legen - Wie und welche Sleep Funktion in welcher Oracle DB Version einsetzen? ===== **Aufgabe**: Eine Datenbank Session soll mit einer sleep Funktion eine gewisse Zeit pausiert werden. Z.B. in einem Monitoring Job, der jede Minute prüfen ob ein andere Job noch aktiv ist und keine Fehler geworfen hat. DBMS_LOCK.sleep - ab 18c bzw. in 19c / 23c auf keine Fall mehr verwenden! Gar nicht mehr offiziell in der Doku! **Möglichkeiten:** **APEX Installiert:** * APEX_UTIL.pause ( aber nur bis maximal 120 Sekunden! , Wait Event "PL/SQL lock timer") **DB Version vor 18c:** * Java über java.lang.Thread.sleep ( Waits Events in der Statistik sichtbar, nicht verwirren lassen!) * DBMS_LOCK.sleep (Sicherheitsproblem mit generellen Zugriff auf das Package DBMS_LOCK, Wait Event "PL/SQL lock timer")) **DB Version ab 18c** * **DBMS_SESSION.sleep** ( so sollte es schon immer sein ... ) ---- ====APEX_UTIL.pause==== Ist in der DB APEX installiert, kann auf APEX_UTIL zurückgegriffen werden. 10 Sekunden warten: begin APEX_UTIL.PAUSE(10); end; / Jetzt ist nur die Frage, wie das intern umgesetzt wird. Das Synonym "APEX_UTIL" zeigt jedenfalls auf das Package "htmldb_util" und von da auf "WWV_FLOW_UTILITIES.PAUSE", dort wird dann "SYS.DBMS_LOCK.SLEEP(L_SECONDS);" gekapselt. Und alle Werte über 120 Sekunden werden mit dem Default 120 Sekunden überschrieben! D.h. die maximale Wartezeit beträgt 120 Sekunden. Eigene Routine um auch länger als 120s warten zu können und dann ab der Version 18 gleich DBMS_SESSION.sleep verwenden zu können: -- CREATE OR REPLACE PROCEDURE sleep_plsql(p_seconds IN NUMBER) IS v_parts pls_integer; v_LastRound pls_integer; v_max_pos_seconds pls_integer:=120; BEGIN $IF DBMS_DB_VERSION.VER_LE_12 $THEN v_max_pos_seconds:=120; $ELSE v_max_pos_seconds:=3600; $END IF p_seconds/v_max_pos_seconds > 1 THEN --- floor largest integer equal to or less than v_parts:=FLOOR(p_seconds/v_max_pos_seconds); -- return the remainder v_LastRound:=MOD( p_seconds, v_max_pos_seconds ); ELSE v_parts:=0; v_LastRound:=p_seconds; END IF; -- if we are on 18 use the DBMS_SESSION sleep methode! $IF DBMS_DB_VERSION.VER_LE_12 $THEN -- we need an oracle apex security context .-( -- put in your workspace apex_util.set_security_group_id (p_security_group_id => apex_util.find_security_group_id (p_workspace => 'GPI')); -- APEX_UTIL.PAUS cann only sleep may 120s! -- IF v_parts > 0 THEN FOR i IN 1..v_parts loop APEX_UTIL.PAUSE(120); END loop; END IF; --- IF v_LastRound > 0 THEN APEX_UTIL.PAUSE(v_LastRound); END IF; $ELSE -- DBMS_SESSION can only sleep may 3600s! -- IF v_parts > 0 THEN FOR i IN 1..v_parts loop DBMS_SESSION.sleep(3600); END loop; END IF; --- IF v_LastRound > 0 THEN DBMS_SESSION.sleep(v_LastRound); END IF; $END END; / --- testen set serveroutput on begin dbms_output.put_line('-- Info start at '||to_char(systimestamp,'hh24:mi:SSxFF')); sleep_plsql(11); dbms_output.put_line('-- Info start at '||to_char(systimestamp,'hh24:mi:SSxFF')); sleep_plsql(119); dbms_output.put_line('-- Info start at '||to_char(systimestamp,'hh24:mi:SSxFF')); sleep_plsql(123); dbms_output.put_line('-- Info start at '||to_char(systimestamp,'hh24:mi:SSxFF')); sleep_plsql(245); dbms_output.put_line('-- Info start at '||to_char(systimestamp,'hh24:mi:SSxFF')); sleep_plsql(545); dbms_output.put_line('-- Info start at '||to_char(systimestamp,'hh24:mi:SSxFF')); end; / ===Problem ORA-20987: APEX - Package variable g_security_group_id must be set ==== Fehler: ORA-20987: APEX - Package variable g_security_group_id must be set. - Contact your application administrator. Lösung, Security ID setzen! -- we need an oracle apex security context .-( -- put in your workspace apex_util.set_security_group_id (p_security_group_id => apex_util.find_security_group_id (p_workspace => 'GPI')); Das ist dann leider nicht so schön, damit wird der Code doch sehr von der Umgebung abhängig. === Wait Event - PL/SQL lock timer === In der DB ist ** "PL/SQL lock timer"** der Wait Event hinter diesem Aufruf in den Wait Statistiken. ---- ==== Java ==== Verwendung der Java Klasse "java.lang.Thread" create or replace PROCEDURE sleep_java(p_milli_seconds in number) AS LANGUAGE JAVA NAME 'java.lang.Thread.sleep(long)'; **Nachteil:** In den AWR / Statspack Berichten wird dann OVJM gerne als häufigster Wait aufgezeigt, sind noch andere Java Komponenten im Einsatz verfälscht das stark das Ergebniss. Wie: {{ :prog:oracle_ovjm_waits_with_java_sleep.png | OVJM Waits mit java.lang.Thread.sleep in der Datenbank }} ---- ====DBMS_LOCK.sleep==== Nachteil: * Sicherheitsprobleme mit DBMS_LOCK habe dazu geführt das her keine Public Synonym in der DB für dieses Package vorliegt und damit ein direkter Grant benötigt wird! Ab 18c auf keine Fall mehr verwenden! Gar nicht mehr offiziell in der Doku! ---- ====DBMS_SESSION.sleep==== Ab der Version 18c! Session "schläft" für n Sekunden, der Maximal Wert beträgt 3600 Sekunden (ansonsten gibt es ein ORA-38148: invalid time limit specified!). Aus der Doku //"The smallest increment can be entered in hundredths of a second; for example, 1.95 is a legal time value." // Beispiel begin dbms_session.sleep(10.45); end; === Problem! nur bis 3600s möglich! === SYS@GPI-?>BEGIN 2 dbms_session.sleep(3601); 3 END; 4 / BEGIN * ERROR at line 1: ORA-38148: invalid time limit specified ORA-06512: at "SYS.DBMS_SYS_ERROR", line 79 ORA-06512: at "SYS.DBMS_SESSION", line 432 ORA-06512: at line 2 Siehe dazu den Code oben mit APEX, hier nochmal die wichigen Teile für eine Umgebung die noch 12c und 19c für die Software verwendt: PROCEDURE sleep_plsql(p_seconds in number) is v_parts pls_integer:=0; v_LastRound pls_integer:=0; v_max_pos_seconds pls_integer:=120; v_use_java boolean:=false; BEGIN $IF DBMS_DB_VERSION.VER_LE_12 $THEN v_max_pos_seconds:=120; $ELSE v_max_pos_seconds:=3600; $END IF p_seconds/v_max_pos_seconds > 1 THEN --- floor largest integer equal to or less than v_parts:=FLOOR(p_seconds/v_max_pos_seconds); -- return the remainder v_LastRound:=MOD( p_seconds, v_max_pos_seconds ); ELSE v_parts:=0; v_LastRound:=p_seconds; END IF; -- if we are on 18 use the DBMS_SESSION sleep methode! $IF DBMS_DB_VERSION.VER_LE_12 $THEN ... use apex sleep see code above $ELSE -- DBMS_SESSION can only sleep may 3600s! -- IF v_parts > 0 THEN FOR i IN 1..v_parts loop DBMS_SESSION.sleep(3600); END loop; END IF; --- IF v_LastRound > 0 THEN DBMS_SESSION.sleep(v_LastRound); END IF; $END end; ---- ==== Quellen ==== * https://oracle-base.com/articles/18c/dbms_session-sleep-18c#dbms_lock * https://stackoverflow.com/questions/2561671/sleep-function-in-oracle * https://oracledeli.wordpress.com/2017/03/31/plsql-sleep-without-using-dbms_lock/