Oracle
 sql >> Datenbank >  >> RDS >> Oracle

Kann es bei der gleichen Zugriffsmethode zu einem Deadlock kommen?

Die "Reihenfolge" ist aus Ihrer Sicht deterministisch nur wenn Sie ORDER BY in Ihre Abfrage aufnehmen. Ob es aus der Sicht des Servers deterministisch ist ist ein Implementierungsdetail, auf das man sich nicht verlassen kann.

Was das Sperren betrifft, so können sich zwei identische DML-Anweisungen gegenseitig blockieren (aber nicht blockieren). Zum Beispiel:

CREATE TABLE THE_TABLE (
    ID INT PRIMARY KEY
);

Transaktion A:

INSERT INTO THE_TABLE VALUES(1);

Transaktion B:

INSERT INTO THE_TABLE VALUES(1);

An diesem Punkt wird Transaktion B angehalten bis Transaktion A entweder festgeschrieben oder zurückgesetzt wird. Wenn A festschreibt, schlägt B aufgrund einer PRIMARY KEY-Verletzung fehl. Wenn A zurückrollt, ist B erfolgreich.

Ähnliche Beispiele können für UPDATE und DELETE erstellt werden.

Der wichtige Punkt ist, dass die Blockierung nicht vom Ausführungsplan abhängt – egal, wie Oracle Ihre Abfrage optimiert, Sie haben immer das gleiche Blockierungsverhalten. Weitere Informationen finden Sie unter Automatische Sperren in DML-Vorgängen.

Wie für tot -Sperren, sie können mit mehreren Anweisungen erreicht werden. Zum Beispiel:

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

Oder möglicherweise mit Anweisungen, die mehr als eine Zeile in unterschiedlicher Reihenfolge ändern, und einem sehr unglücklichen Timing (kann das jemand bestätigen?).

--- AKTUALISIEREN ---

Lassen Sie mich als Antwort auf die Aktualisierung Ihrer Frage eine allgemeine Beobachtung machen:Wenn gleichzeitige Threads der Ausführung Objekte in der konsistenten Reihenfolge sperren , Deadlocks sind unmöglich. Dies gilt für jede Art von Sperren, seien es Mutexe in einem durchschnittlichen Multithread-Programm (siehe z. B. Herb Sutters Gedanken zu Sperrhierarchien) oder Datenbanken. Sobald Sie die Reihenfolge so ändern, dass zwei beliebige Sperren "umgedreht" werden, besteht die Möglichkeit von Deadlocks.

Ohne den Index zu scannen, aktualisieren Sie (und sperren ) Zeilen in einer Reihenfolge und mit dem Index in einer anderen. In Ihrem Fall passiert also wahrscheinlich Folgendes:

  • Wenn Sie den Index-Scan für beide gleichzeitigen Transaktionen deaktivieren , sperren beide Zeilen in derselben Reihenfolge [X], sodass kein Deadlock möglich ist.
  • Wenn Sie den Index-Scan nur für eine Transaktion aktivieren , sperren sie Zeilen nicht mehr in derselben Reihenfolge, daher besteht die Möglichkeit eines Deadlocks.
  • Wenn Sie den Index-Scan für beide Transaktionen aktivieren , dann wiederum sperren beide Zeilen in der gleichen Reihenfolge, und ein Deadlock ist unmöglich (machen Sie weiter und versuchen Sie alter session set optimizer_index_cost_adj = 1; in beiden Sitzungen und Sie werden sehen).

[X] Obwohl ich mich nicht darauf verlassen würde, dass vollständige Tabellenscans eine garantierte Reihenfolge haben - es könnte einfach daran liegen, wie das aktuelle Oracle unter diesen spezifischen Umständen funktioniert, und einige zukünftige Oracle oder andere Umstände könnten ein anderes Verhalten erzeugen.

Das Vorhandensein von Index ist also zufällig - das eigentliche Problem ist die Bestellung. Es ist einfach so, dass die Reihenfolge in UPDATE durch einen Index beeinflusst werden kann, aber wenn wir die Reihenfolge auf andere Weise beeinflussen könnten, würden wir ähnliche Ergebnisse erhalten.

Da UPDATE kein ORDER BY hat, können Sie die Sperrreihenfolge nicht wirklich durch UPDATE allein garantieren. Wenn Sie sich jedoch trennen Updates sperren, dann können Sie die Sperrreihenfolge garantieren:

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

Während Ihr ursprünglicher Code Deadlocks in meiner Oracle 10-Umgebung verursachte, tut der folgende Code dies nicht:

Sitzung 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;
/

Sitzung 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;
/