25. April 2008

Datenbank-Sessions synchronisieren: DBMS_ALERT

English title: Synchronizing database sessions: DBMS_ALERT

Die Oracle-Datenbank kommt bekanntlich mit einer Menge PL/SQL-Pakete daher und es ist fast unmöglich, immer mit allen Neuerungen Schritt zu halten. Nun ist mir mal wieder ein Kandidat untergekommen, der so kaum bekannt ist: DBMS_ALERT. Mit DBMS_ALERT können Datenbank-Sessions sich untereinander durch das Versenden und Empfangen von Events synchronisieren. Eine Session kann sich für bestimmte Ereignisse registrieren und dann auf das Auftreten eines Ereignisses warten ... Sobald das Ereignis eintritt, geht die Verarbeitung weiter. DBMS_ALERT basiert auf dem "normalen" Transaktionsverhalten der Datenbank - es nutzt "normale" Locks zum Synchnonisieren der Sessions und man muss das Senden eines Ereignis auch durch ein COMMIT bestätigen (DBMS_PIPE wäre eine nicht-Transaktionale Alternative).
It is very well known that the Oracle database has a very rich function libary and it's kind of difficult to keep track with all the new PL/SQL packages and functions. Now I've seen a candidate which is not very common and I like to write a few lines about it. DBMS_ALERT allows to synchronize different database sessions using the "normal" database transaction mechanism. A session may register for an event and after this the session can wait for the occurance of this event. If another session signals the event the first session awakes and proceeds its opperation. As the database transaction mechanism is used the signalling has to be committed (as we'll see). If you're looking for a non-transactional alternative, have a look into DBMS_PIPE.
Probieren wir das mal aus: Dazu brauchen wir zwei Datenbank-Sessions - es muss nicht unbedingt der gleiche Datenbankuser verwendet werden. Da das DBMS_ALERT-Paket dem SYS-User gehört, müssen Sie vor ggfs. EXECUTE-Privilegien daran vergeben. Starten Sie anschließend zwei Datenbank-Sessions (am besten mit SQL*Plus).
Let's have a try on that. All we need is two database sessions (can be different database users) and EXECUTE privilege on DBMS_ALERT (this is owned by SYS). This example uses SQL*Plus for the two database sessions.
Session 1 ist die Session, die auf eine andere warten soll - daher muss sie sich zuerst als "Interessent" für ein Ereignis registrieren:
Session 1 should wait for the event - so it must subscribe to this event by using DBMS_ALERT.REGISTER:
begin
 dbms_alert.register('TESTEVENT');
end;
/
Anschließend warten wir auf das Ereignis mit DBMS_ALERT.WAITONE. Diese Prozedur nimmt zunächst den Namen des Ereignisses auf das gewartet werden soll, auf. Die nächsten zwei OUT-Parameter (message und status geben zusätzliche Informationen, wenn die Prozedur "zurückkehrt". DBMS_ALERT.WAITONE kehrt zurück, wenn entweder das Ereignis auftritt oder der Timeout abläuft. Der OUT-Parameter status enthält dann den Wert 0 wenn das Ereignis aufgertreten ist und 1 wenn der Timeout abgelaufen ist. Gibt man keinen Parameter timeout an, so wartet die Prozedur auf jeden Fall solange, bis das Ereignis auftritt. In diesem Beispiel nutzen wir zwei SQL*Plus-Variablen ("msg" und "status") - darin werden die Werte der bneiden OUT-Parameter message und status abgelegt. (Wichtig: Dieser Aufruf löst ein implizites COMMIT aus)...
Next the session is going to wait for the occurrance of TESTEVENT. It uses DBMS_ALERT.WAITONE for this. This procedure takes the name of the event to wait for as the first parameter. The next two OUT-Parameters message and status give event information when the procedure returns. DBMS_WAITONE returns either when the event was being signalled or when the timeout occured. The OUT parameter status contains 0 if the session received the event and 1 if the procedure just timed out. If the parameter tomeout is not specified the procedure waits forever. In this example we define two SQL*Plus variables ("msg" and "status") to receive the OUT parameters message and status. (Important: The procedure issues an implicit COMMIT) ...
var msg     varchar2(200);
var status  number;

begin
 dbms_alert.waitone(
   name    => 'TESTEVENT',
   message => :msg,
   status  => :status
 );
end;
/
... und nun wartet die Session ... und zwar in diesem Beispiel solange, bis von einer anderen Session aus das Ereignis TESTEVENT ausgelöst wird. Also neue Datenbanksession aufmachen und folgendes absetzen:
... and now this session will wait ... in this example until TESTEVENT will be signalled from another session. So we open Session 2 and issue the following:
begin
 dbms_alert.signal(
   name    => 'TESTEVENT',
   message => 'This is a message / Dies ist eine Nachricht'
 );
end;
/
Nun passiert zunächst noch nichts ... wenn aber dann ein COMMIT abgesetzt wird, erwacht die Session 1 wieder zum Leben ...
Seems not to work ... Session 1 is still waiting - but after issuing COMMIT from Session 2 the first Session 1 wakes up and is alive.
:
~~~ wait ~~~
:

PL/SQL-Prozedur erfolgreich abgeschlossen.

Abgelaufen: 00:00:06.39
Die Inhalte der Bind-Variablen "msg" und "status" kann man mit dem SQL*Plus-Kommandi print anzeigen lassen:
Let's have a look at the SQL*Plus variables "msg" and "status" using the print command:
SQL> print

MSG
-------------------------------------------
This is a message / Dies ist eine Nachricht


    STATUS
----------
         0
Neben der Prozedur WAITONE gibt es noch WAITANY. Der Name des Ereignis ist dann ein OUT-Parameter, da WAITANY auf ein beliebiges Ereignis (die Session muss sich lediglich registriert haben) wartet. Wenn ein Ereignis eingetreten ist, wird dessen Name im Out-Parameter name zurückgegeben.
Besides WAITONE there is the WAITANY procedure. As its name indicates this procedure waits for any event the session has registered to. So we don't provide a name for the event to wait for - the parameter name is now an OUT parameter providing the signalled event instead.
Wichtig ist übrigens das COMMIT nach dem Aufruf von SIGNAL. Grund ist, dass pro Ereignis alle Aufrufe von SIGNAL serialisiert werden. Setzt also eine andere Session ebenfalls ein SIGNAL für das gleiche Ereignis ab, wartet diese solange, bis der erste Aufruf von SIGNAL committed wurde.
The COMMIT after the SIGNAL call is quite important. The reason is that for one event (here: TESTEVENT) all SIGNAL calls will be serialized. A second SIGNAL call for TESTEVENT will therefore wait until the first one is being committed. As said in the beginning: DBMS_ALERT is transactional.
Bleibt noch das Aufräumen. Mit DBMS_ALERT.REMOVE entfernt eine Session ihre Registrierung für ein Ereignis wieder ...
So we only have to cleanup now. A session unsubscribes from an event using the DBMS_ALERT.REMOVE call ...

Kommentare:

Anonym hat gesagt…

Bei waitany heißt es:

"(Wichtig: Dieser Aufruf löst ein implizites COMMIT aus)... "

Stimmt das? Wo ist dieses Verhalten dokumentiert? Ich kann in der Oracle Doku nichts dazu finden.

Carsten Czarski hat gesagt…

Hallo,

das steht in der "PL/SQL Package Reference" bei der Dokumentation von DBMS_ALERT.WAITANY:

http://download.oracle.com/docs/cd/B28359_01/appdev.111/b28419/d_alert.htm#i997060

Dort bei den Usage Notes findet sich der entsprechende Hinweis.

Grüße

-Carsten

Beliebte Postings