=====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/