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

Può verificarsi un deadlock con lo stesso metodo di accesso?

L'"ordine" è deterministico dal tuo punto di vista solo se includi ORDER BY nella tua query. Se è deterministico dal punto di vista del server è un dettaglio di implementazione, su cui non si può fare affidamento.

Per quanto riguarda il blocco, due istruzioni DML identiche possono bloccarsi (ma non bloccarsi) a vicenda. Ad esempio:

CREATE TABLE THE_TABLE (
    ID INT PRIMARY KEY
);

Transazione A:

INSERT INTO THE_TABLE VALUES(1);

Transazione B:

INSERT INTO THE_TABLE VALUES(1);

A questo punto, la transazione B è bloccata fino a quando la transazione A non esegue il commit o il rollback. Se A si impegna, B fallisce a causa della violazione della CHIAVE PRIMARIA. Se A tira indietro, B ha successo.

Esempi simili possono essere costruiti per UPDATE e DELETE.

Il punto importante è che il blocco non dipenderà dal piano di esecuzione:indipendentemente da come Oracle sceglie di ottimizzare la query, avrai sempre lo stesso comportamento di blocco. Potresti voler leggere i blocchi automatici nelle operazioni DML per ulteriori informazioni.

Quanto ai morti -locks, sono possibili con più istruzioni. Ad esempio:

A: INSERT INTO THE_TABLE VALUES(1);
B: INSERT INTO THE_TABLE VALUES(2);
A: INSERT INTO THE_TABLE VALUES(2);
B: INSERT INTO THE_TABLE VALUES(1); -- SQL Error: ORA-00060: deadlock detected while waiting for resource

Oppure, possibilmente con istruzioni che modificano più di una riga in ordine diverso e con tempi molto sfortunati (qualcuno potrebbe confermarlo?).

--- AGGIORNAMENTO ---

In risposta all'aggiornamento della tua domanda, consentitemi di fare un'osservazione generale:se i thread simultanei di esecuzione bloccano gli oggetti nell'ordine coerente , i deadlock sono impossibili. Questo è vero per qualsiasi tipo di blocco, che si tratti di mutex nel tuo programma multi-thread medio (ad es. Vedi i pensieri di Herb Sutter sulle gerarchie di blocco) o di database. Dopo aver modificato l'ordine in modo tale da "capovolgere" due blocchi qualsiasi, viene introdotta la possibilità di deadlock.

Senza eseguire la scansione dell'indice, stai aggiornando (e bloccando ) righe in un ordine e con l'indice in un altro. Quindi, questo è probabilmente ciò che accade nel tuo caso:

  • Se disabilita la scansione dell'indice per entrambe le transazioni simultanee , bloccano entrambe le righe nello stesso ordine [X], quindi non è possibile alcun deadlock.
  • Se abiliti la scansione dell'indice per una sola transazione , non bloccano più le righe nello stesso ordine, da qui il rischio di un deadlock.
  • Se abiliti la scansione dell'indice per entrambe le transazioni , quindi entrambi stanno bloccando le righe nello stesso ordine e un deadlock è impossibile (vai avanti e prova alter session set optimizer_index_cost_adj = 1; in entrambe le sessioni e vedrai).

[X] Anche se non farei affidamento su scansioni di tabelle complete con un ordine garantito, potrebbe essere semplicemente il modo in cui funziona Oracle corrente in queste circostanze specifiche e alcuni Oracle futuri o circostanze diverse potrebbero produrre un comportamento diverso.

Quindi, la presenza dell'indice è accidentale:il vero problema è l'ordine. Succede che l'ordine in UPDATE può essere influenzato da un indice, ma se potessimo influenzare l'ordine in un altro modo, otterremmo risultati simili.

Poiché UPDATE non ha ORDER BY, non puoi davvero garantire l'ordine di blocco solo tramite UPDATE. Tuttavia, se separare bloccando l'aggiornamento, allora puoi garantire l'ordine di blocco:

SELECT ... ORDER BY ... FOR UPDATE;

Mentre il tuo codice originale ha causato deadlock nel mio ambiente Oracle 10, il codice seguente non lo fa:

Sessione 1:

declare
    cursor cur is select * from deadlock_test where a > 0 order by a for update;
begin
    while true loop
        for locked_row in cur loop
            update deadlock_test set a = -99999999999999999999 where current of cur;
        end loop;
        rollback;
    end loop;
end;
/

Sessione 2:

alter session set optimizer_index_cost_adj = 1;

declare
    cursor cur is select * from deadlock_test where a > 0 order by a for update;
begin
    while true loop
        for locked_row in cur loop
            update deadlock_test set a = -99999999999999999999 where current of cur;
        end loop;
        rollback;
    end loop;
end;
/