Это не проблема с 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.Возможно ли это для вас, зависит от используемой вами платформы.