Questo non è un problema con MERGE in quanto tale. Piuttosto il problema sta nella tua applicazione. Considera questa procedura memorizzata:
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;
Quindi, questo è l'equivalente PL/SQL di un MERGE su T23. Cosa succede se due sessioni lo chiamano contemporaneamente?
SSN1> exec upsert_t23(100, 'FOX IN SOCKS')
SSN2> exec upsert_t23(100, 'MR KNOX')
SSN1 arriva per primo, non trova alcun record corrispondente e inserisce un record. SSN2 arriva secondo ma prima che SSN1 si impegni, non trova alcun record, inserisce un record e si blocca perché SSN1 ha un blocco sul nodo dell'indice univoco per 100. Quando SSN1 esegue il commit, SSN2 lancerà una violazione DUP_VAL_ON_INDEX.
L'istruzione MERGE funziona esattamente allo stesso modo. Entrambe le sessioni verificheranno on (t23.id = 100)
, non trovarlo e scendi nel ramo INSERT. La prima sessione avrà esito positivo e la seconda lancerà ORA-00001.
Un modo per gestirlo è introdurre il blocco pessimistico. All'inizio della procedura UPSERT_T23 blocchiamo la tabella:
...
lock table t23 in row shared mode nowait;
open c;
...
Ora arriva SSN1, afferra la serratura e procede come prima. Quando arriva SSN2 non può ottenere il blocco, quindi fallisce immediatamente. Il che è frustrante per il secondo utente, ma almeno non si blocca, inoltre sa che qualcun altro sta lavorando allo stesso record.
Non esiste una sintassi per INSERT che equivale a SELECT ... FOR UPDATE, perché non c'è nulla da selezionare. E quindi non esiste nemmeno una tale sintassi per MERGE. Quello che devi fare è includere l'istruzione LOCK TABLE nell'unità di programma che emette MERGE. Se questo è possibile per te dipende dal framework che stai utilizzando.