Oracle
 sql >> Database >  >> RDS >> Oracle

Posso fare un MERGE atomico in Oracle?

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.