Dit is geen probleem met MERGE als zodanig. Het probleem ligt eerder in uw toepassing. Overweeg deze opgeslagen 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;
Dit is dus het PL/SQL-equivalent van een MERGE op T23. Wat gebeurt er als twee sessies het tegelijkertijd oproepen?
SSN1> exec upsert_t23(100, 'FOX IN SOCKS')
SSN2> exec upsert_t23(100, 'MR KNOX')
SSN1 komt daar als eerste aan, vindt geen overeenkomend record en voegt een record in. SSN2 komt daar als tweede, maar voordat SSN1 commit, vindt geen record, voegt een record in en loopt vast omdat SSN1 een slot heeft op het unieke indexknooppunt voor 100. Wanneer SSN1 zich begaat, zal SSN2 een DUP_VAL_ON_INDEX-overtreding veroorzaken.
De MERGE-instructie werkt op precies dezelfde manier. Beide sessies controleren on (t23.id = 100)
, vind het niet en ga naar de INSERT-tak. De eerste sessie zal slagen en de tweede zal ORA-00001 slingeren.
Een manier om hiermee om te gaan, is door pessimistische vergrendeling te introduceren. Aan het begin van de UPSERT_T23 procedure vergrendelen we de tafel:
...
lock table t23 in row shared mode nowait;
open c;
...
Nu arriveert SSN1, grijpt het slot en gaat verder zoals voorheen. Wanneer SSN2 arriveert, kan het het slot niet krijgen, dus het mislukt onmiddellijk. Dat is frustrerend voor de tweede gebruiker, maar ze hangen tenminste niet op, en ze weten ook dat iemand anders aan hetzelfde record werkt.
Er is geen syntaxis voor INSERT die gelijk is aan SELECT ... FOR UPDATE, omdat er niets te selecteren is. En dus is er ook niet zo'n syntaxis voor MERGE. Wat u moet doen is de LOCK TABLE-instructie opnemen in de programma-eenheid die de MERGE uitgeeft. Of dit voor jou mogelijk is, hangt af van het framework dat je gebruikt.