Este não é um problema com MERGE como tal. Em vez disso, o problema está no seu aplicativo. Considere este procedimento armazenado:
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;
Então, este é o equivalente PL/SQL de um MERGE em T23. O que acontece se duas sessões o chamarem simultaneamente?
SSN1> exec upsert_t23(100, 'FOX IN SOCKS')
SSN2> exec upsert_t23(100, 'MR KNOX')
O SSN1 chega primeiro, não encontra nenhum registro correspondente e insere um registro. O SSN2 chega em segundo lugar, mas antes do commit do SSN1, não encontra nenhum registro, insere um registro e trava porque SSN1 tem um bloqueio no nó de índice exclusivo para 100. Quando SSN1 confirma, SSN2 lançará uma violação DUP_VAL_ON_INDEX.
A instrução MERGE funciona exatamente da mesma maneira. Ambas as sessões verificarão
on (t23.id = 100)
, não o encontre e desça a ramificação INSERT. A primeira sessão será bem-sucedida e a segunda lançará ORA-00001. Uma maneira de lidar com isso é introduzir o bloqueio pessimista. No início do procedimento UPSERT_T23 bloqueamos a tabela:
...
lock table t23 in row shared mode nowait;
open c;
...
Agora, SSN1 chega, pega a fechadura e continua como antes. Quando o SSN2 chega, ele não consegue o bloqueio, então ele falha imediatamente. O que é frustrante para o segundo usuário, mas pelo menos eles não estão pendurados, além de saberem que outra pessoa está trabalhando no mesmo registro.
Não há sintaxe para INSERT que seja equivalente a SELECT ... FOR UPDATE, porque não há nada para selecionar. E, portanto, também não existe essa sintaxe para MERGE. O que você precisa fazer é incluir a instrução LOCK TABLE na unidade do programa que emite o MERGE. Se isso é possível para você depende da estrutura que você está usando.