DBMS_LOCK zum Sicherstellen von Integritätsbedingungen. Zur Demonstration wollen wir sicherstellen, dass pro Abteilung nur maximal ein Manager existiert. Wie bereit im vergangenen Post wird ein Compound Trigger verwendet:create trigger check_manager_c_iu for insert or update of job, deptno on emp
when (new.job = 'MANAGER' or old.deptno != new.deptno)
compound trigger
type departments_nt is table of dept.deptno%type;
departments_tab departments_nt := departments_nt();
before each row is
begin
departments_tab.extend;
departments_tab( departments_tab.last ) := :new.deptno;
end before each row;
after statement is
v_manager_count integer := 0;
begin
departments_tab := set( departments_tab );
for i in 1 .. departments_tab.count loop
select
count(*)
into
v_manager_count
from
emp
where
job = 'MANAGER' and deptno = departments_tab(i);
if ( v_manager_count > 1 ) then
raise_application_error
(
-20010,
'No more than one manager allowed in department ' || departments_tab(i) || '.'
);
end if;
end loop;
end after statement;
end check_manager_c_iu;
/
Der BEFORE EACH ROW Teil speichert die Abteilungen in einer Nested Table, während der AFTER STATEMENT Teil die eigentliche Überprüfung vornimmt. Jedoch besteht immer noch die Möglichkeit, dass die Bedinung verletzt wird.Zur Demonstration dienen zwei Sessions (T1, T2), welche die folgenden Operationen ausführen:
t0 T1:
INSERT MANAGER 'X' IN DEPARTMENT 40t1 T2:
INSERT MANAGER 'Y' IN DEPARTMENT 40t2 T1:
COMMIT;t3 T2:
COMMIT;Anmerkung: Wir nehmen an, dass Abteilung 40 vor dem Zeitpunkt t0 keinen Manager besitzt.
Der INSERT-Befehl zum Zeitpunkt t0 funktioniert, da noch kein Manager für Abteilung 40 existiert. Jedoch geht auch der zweite INSERT-Befehl von T2 durch, da T1 noch kein
COMMIT ausgeführt hat. Nach Zeitpunkt t3 existieren somit zwei Manager in Abteilung 40; eine Verletzung der geforderten Bedingung. Eine mögliche Lösung für dieses Problem stellt
DBMS_LOCK in Form der Prozedur ALLOCATE_UNIQUE und der Funktion REQUEST bereit. ALLOCATE_UNIQUE ordnet einem Namen eine eindeutige Nummer zu und gibt diese zurück. Die eigentliche Anfrage für diesen benannten Lock wird durch die Funktion REQUEST ausgeführt. Da die Prozedur ALLOCATE_UNIQUE einen impliziten COMMIT durchführt ist eine autonome Transaktion erforderlich.Das nachfolgende Code Listing enthält eine Funktion, welche
ALLOCATE_UNIQUE in einer anderen Transaktion aufruft und den Lock Handle zurückliefert. Die Prozedur REQUEST_LOCK stellt dann die Anfrage für den Lock im exclusive mode.create function allocate_unique(p_lockname_in in varchar2) return varchar2 is pragma autonomous_transaction; v_lockhandle varchar2(128); begin dbms_lock.allocate_unique( upper(p_lockname_in), v_lockhandle, 60*10 ); return v_lockhandle; end; / create procedure request_lock(p_lockname_in in varchar2) is v_lockhandle varchar2(128); v_return_code number; begin v_lockhandle := allocate_unique(p_lockname_in); v_return_code := dbms_lock.request( lockhandle => v_lockhandle, lockmode => dbms_lock.x_mode, timeout => 10, release_on_commit => true ); if pl_return not in (0,4) then raise_application_error ( -20010, 'Unable to get the requested lock ' || p_lockname_in || '.'); end if; end; /Eine Möglichkeit besteht nun darin, die Prozedur
REQUEST_LOCK im AFTER STATEMENT Teil des Triggers aufzurufen; der Lock trägt den Namen CHECK_MANAGER....
after statement is
v_manager_count integer := 0;
begin
request_lock('CHECK_MANAGER');
...
end after statement;
...
Damit ist das Problem zwar gelöst, jedoch kann das Locking noch granularer gestaltet werden. Dazu wird ein Lock pro Abteilung definiert, wie das folgende Listing zeigt.... after statement is v_manager_count integer := 0; begin departments_tab := set( departments_tab ); for i in 1 .. departments_tab.count loop request_lock( 'CHECK_MANAGER_DEPT_' || departments_tab(i) ); ... end loop; end after statement; ...Das ganze soll nun mithilfe von zwei Sessions getestet werden.
T1:
insert into emp (empno, ename, job, mgr, hiredate, sal, deptno) values (7974, 'CLARKSON', 'MANAGER', 7839, sysdate, 3750, 40); -- 1 rows insertedT2:
insert into emp (empno, ename, job, mgr, hiredate, sal, deptno) values (7975, 'JOHNSON', 'MANAGER', 7839, sysdate, 3500, 40); -- WAIT (maximal 10 Sekunden, wie in der Prozedur REQUEST_LOCK angegeben)T1:
commit; -- weniger als 10 Sekunden nach dem INSERTT2:
--SQL-Fehler: ORA-20010: No more than one manager allowed in department 40. --ORA-06512: in "EXAMPLE.CHECK_MANAGER_C_IU", Zeile 27 --ORA-04088: Fehler bei der Ausführung von Trigger 'EXAMPLE.CHECK_MANAGER_C_IU'Das Beispiel und die Verwendung von DBMS_LOCK ist angelehnt an das Buch "Applied Mathematics for Database Professionals" von Lex de Haan und Toon Koppelaars. Dort wird das Ganze noch detaillierter und mit zahrleichen Beispielen samt der notwendigen Theorie behandelt.
Keine Kommentare:
Kommentar veröffentlichen