Dette er ikke et problem med MERGE som sådan. Problemet ligger snarere i din ansøgning. Overvej denne lagrede procedure:
create or replace procedure upsert_t23
( p_id in t23.id%type
, p_name in t23.name%type )
is
cursor c is
select null
from t23
where id = p_id;
dummy varchar2(1);
begin
open c;
fetch c into dummy;
if c%notfound then
insert into t23
values (p_id, p_name);
else
update t23
set name = p_name
where id = p_id;
end if;
end;
Så dette er PL/SQL-ækvivalenten til en MERGE på T23. Hvad sker der, hvis to sessioner kalder det samtidigt?
SSN1> exec upsert_t23(100, 'FOX IN SOCKS')
SSN2> exec upsert_t23(100, 'MR KNOX')
SSN1 kommer der først, finder ingen matchende post og indsætter en post. SSN2 kommer der nummer to, men før SSN1 begår, finder ingen post, indsætter en post og hænger fordi SSN1 har en lås på den unikke indeksnode til 100. Når SSN1 begår, vil SSN2 slynge en DUP_VAL_ON_INDEX overtrædelse.
MERGE-erklæringen fungerer på nøjagtig samme måde. Begge sessioner vil tjekke on (t23.id = 100)
, ikke finde det og gå ned i INSERT-grenen. Den første session vil lykkes, og den anden vil kaste ORA-00001.
En måde at håndtere dette på er at indføre pessimistisk låsning. Ved starten af UPSERT_T23-proceduren låser vi tabellen:
...
lock table t23 in row shared mode nowait;
open c;
...
Nu ankommer SSN1, griber låsen og fortsætter som før. Når SSN2 ankommer, kan den ikke få låsen, så den fejler med det samme. Hvilket er frustrerende for den anden bruger, men de hænger i det mindste ikke, plus de ved, at en anden arbejder på den samme post.
Der er ingen syntaks for INSERT, som svarer til SELECT ... FOR UPDATE, fordi der ikke er noget at vælge. Og så er der heller ikke en sådan syntaks for MERGE. Det, du skal gøre, er at inkludere LOCK TABLE-sætningen i den programenhed, der udsteder FLÉNING. Om dette er muligt for dig afhænger af den ramme, du bruger.