Detta är inte ett problem med MERGE som sådan. Snarare ligger problemet i din ansökan. Tänk på denna lagrade procedur:
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å detta är PL/SQL-motsvarigheten till en MERGE på T23. Vad händer om två sessioner anropar det samtidigt?
SSN1> exec upsert_t23(100, 'FOX IN SOCKS')
SSN2> exec upsert_t23(100, 'MR KNOX')
SSN1 kommer dit först, hittar ingen matchande post och infogar en post. SSN2 kommer dit tvåa men innan SSN1 commit, hittar ingen post, infogar en post och hänger sig eftersom SSN1 har ett lås på den unika indexnoden för 100. När SSN1 begår kommer SSN2 att utlösa en DUP_VAL_ON_INDEX-överträdelse.
MERGE-satsen fungerar på exakt samma sätt. Båda sessionerna kommer att kontrollera on (t23.id = 100)
, inte hitta den och gå ner för INSERT-grenen. Den första sessionen kommer att lyckas och den andra kommer att kasta ORA-00001.
Ett sätt att hantera detta är att införa pessimistisk låsning. I början av UPSERT_T23-proceduren låser vi tabellen:
...
lock table t23 in row shared mode nowait;
open c;
...
Nu anländer SSN1, tar tag i låset och fortsätter som tidigare. När SSN2 anländer kan den inte få låset, så det misslyckas omedelbart. Vilket är frustrerande för den andra användaren, men de hänger sig åtminstone inte, plus att de vet att någon annan arbetar med samma skiva.
Det finns ingen syntax för INSERT som motsvarar SELECT ... FOR UPDATE, eftersom det inte finns något att välja. Och så det finns ingen sådan syntax för MERGE heller. Vad du behöver göra är att inkludera LOCK TABLE-satsen i den programenhet som utfärdar MERGE. Om detta är möjligt för dig beror på vilket ramverk du använder.