Това не е проблем с MERGE като такъв. По-скоро проблемът е във вашето приложение. Помислете за тази съхранена процедура:
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;
И така, това е PL/SQL еквивалентът на MERGE на T23. Какво се случва, ако две сесии го извикат едновременно?
SSN1> exec upsert_t23(100, 'FOX IN SOCKS')
SSN2> exec upsert_t23(100, 'MR KNOX')
SSN1 стига първи там, не намира съответстващ запис и вмъква запис. SSN2 стига там втори, но преди SSN1 да се ангажира, не намира запис, вмъква запис и увисва тъй като SSN1 има заключване на уникалния индексен възел за 100. Когато SSN1 ангажира, SSN2 ще предизвика нарушение на DUP_VAL_ON_INDEX.
Изявлението MERGE работи по абсолютно същия начин. И двете сесии ще проверят on (t23.id = 100)
, а не да го намерите и да отидете надолу в клона INSERT. Първата сесия ще успее, а втората ще хвърли ORA-00001.
Един от начините да се справите с това е да въведете песимистично заключване. В началото на процедурата UPSERT_T23 ние заключваме таблицата:
...
lock table t23 in row shared mode nowait;
open c;
...
Сега SSN1 пристига, грабва ключалката и продължава както преди. Когато SSN2 пристигне, той не може да получи ключалката, така че се проваля незабавно. Което е разочароващо за втория потребител, но поне те не висят, освен това знаят, че някой друг работи върху същия запис.
Няма синтаксис за INSERT, който е еквивалентен на SELECT ... FOR UPDATE, защото няма какво да се избира. И така, няма такъв синтаксис и за MERGE. Това, което трябва да направите, е да включите командата LOCK TABLE в програмния модул, който издава MERGE. Дали това е възможно за вас зависи от рамката, която използвате.