Verfolgen, Debuggen und Beheben von Row Lock-Konflikten

12

In der Nacht habe ich viele Probleme mit Zeilensperren. Der umstrittene Tisch scheint ein bestimmter Tisch zu sein.

Dies ist im Allgemeinen, was passiert -

  • Entwickler 1 startet eine Transaktion über den Oracle Forms-Frontend-Bildschirm
  • Entwickler 2 startet eine andere Transaktion aus einer anderen Sitzung und verwendet dabei denselben Bildschirm

~ 5 Minuten später scheint das Frontend nicht zu reagieren. Das Überprüfen von Sitzungen zeigt Zeilensperrenkonflikte. Die "Lösung", die jeder herumwirft, ist, Sitzungen zu beenden: /

Als Datenbankentwickler

  • Was kann getan werden, um Reihensperrkonflikte zu beseitigen?
  • Wäre es möglich, herauszufinden, welche Zeile einer gespeicherten Prozedur diese Zeilensperrkonflikte verursacht?
  • Was wäre die allgemeine Richtlinie, um solche Probleme zu reduzieren, zu vermeiden oder zu beseitigen, welche Codierung?

Wenn diese Frage zu offen / unzureichend ist, können Sie sie gerne bearbeiten / mich informieren. Ich werde mein Bestes tun, um einige zusätzliche Informationen hinzuzufügen.


Die betreffende Tabelle enthält viele Einfügungen und Aktualisierungen. Ich würde sagen, es handelt sich um eine der am stärksten frequentierten Tabellen. Der SP ist ziemlich komplex - um es zu vereinfachen - er holt Daten aus verschiedenen Tabellen, füllt sie in Arbeitstabellen, viele arithmetische Operationen finden in der Arbeitstabelle statt und das Ergebnis der Arbeitstabelle wird in die betreffende Tabelle eingefügt / aktualisiert.


Die Datenbankversion ist Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - 64bit. Der Ablauf der Logik wird in beiden Sitzungen in derselben Reihenfolge ausgeführt, die Transaktion wird nicht zu lange offen gehalten (oder zumindest denke ich ), und die Sperren treten während der aktiven Ausführung von Transaktionen auf.


Update: Die Anzahl der Tabellenzeilen ist mit etwa 3,1 Millionen Zeilen größer als erwartet. Nach dem Verfolgen einer Sitzung stellte ich außerdem fest, dass einige Aktualisierungsanweisungen für diese Tabelle den Index nicht verwenden. Warum ist es so - ich bin nicht sicher. Die Spalte, auf die in der where-Klausel verwiesen wird, ist indiziert. Ich erstelle gerade den Index neu.

Sathyajith Bhat
quelle
1
@Sathya - können Sie die Komplexität gespeicherter Prozeduren erläutern? Befindet sich die verdächtige Tabelle unter rigorosem Update oder Insert?
CoderHawk
Spielen Fremdschlüssel hier eine Rolle? (Manchmal ist ein Index erforderlich.) Welche Version der Datenbank ist vorhanden? Wird der Ablauf der Logik in beiden Sitzungen in derselben Reihenfolge ausgeführt? Wird die Transaktion für lange Zeit offen gehalten? Tritt die Sperre während der Bedenkzeit des Benutzers oder während der aktiven Ausführung der Transaktion auf?
ik_zelf
@ Sandy Ich habe die Frage aktualisiert
Sathyajith Bhat
@ik_zelf Ich habe die Frage aktualisiert
Sathyajith Bhat
1
Mir ist nicht klar, warum dies ein Problem ist - Oracle tut genau das, was es tun soll, nämlich den Zugriff auf eine einzelne Zeile zu serialisieren. Wenn jemand diese Zeile hat, können Sie die vorherige Version davon lesen, aber um zu schreiben, müssen Sie warten, bis er die Sperre aufhebt. Die einzige "Lösung" besteht darin, entweder a) nicht herumzublödeln und COMMIToder ROLLBACKin einer angemessenen Zeit oder b) so zu arrangieren, dass dieselben Leute nicht immer dieselbe Reihe zur selben Zeit wollen.
Gaius

Antworten:

10

Wäre es möglich herauszufinden, welche Zeile einer gespeicherten Prozedur diese Zeilensperrkonflikte verursacht?

Nicht genau, aber Sie können die SQL-Anweisung abrufen, die die Sperre verursacht, und die zugehörigen Zeilen in der Prozedur identifizieren.

SELECT sid, sql_text
FROM v$session s
LEFT JOIN v$sql q ON q.sql_id=s.sql_id
WHERE state = 'WAITING' AND wait_class != 'Idle'
AND event = 'enq: TX - row lock contention';

Was wäre die allgemeine Richtlinie, um solche Codierungsprobleme zu reduzieren, zu vermeiden oder zu beseitigen?

Im Oracle Concepts Guide-Abschnitt zu Sperren heißt es: "Eine Zeile ist nur gesperrt, wenn sie von einem Writer geändert wurde." Eine weitere Sitzung die gleiche Zeile Aktualisierung wird dann warten , bis die erste Sitzung COMMIToder ROLLBACKbevor es weitergehen kann. Um das Problem zu beheben, können Sie die Benutzer serialisieren. Hier sind jedoch einige Punkte, die das Problem möglicherweise so weit reduzieren können, dass es kein Problem darstellt.

  • COMMIThäufiger. Mit jedem COMMITAufheben von Sperren wird die Wahrscheinlichkeit verringert, dass eine andere Sitzung dieselbe Zeile benötigt, wenn Sie die Aktualisierungen in Stapeln durchführen können.
  • Stellen Sie sicher, dass Sie keine Zeilen aktualisieren, ohne deren Werte zu ändern. Beispielsweise UPDATE t1 SET f1=DECODE(f2,’a’,f1+1,f1);sollte als selektiver umgeschrieben werden (weniger Sperren lesen) UPDATE t1 SET f1=f1+1 WHERE f2=’a’;. Wenn die Änderung der Anweisung weiterhin die meisten Zeilen in der Tabelle sperrt, hat die Änderung natürlich nur einen Vorteil für die Lesbarkeit.
  • Stellen Sie sicher, dass Sie Sequenzen verwenden, anstatt eine Tabelle zu sperren, um eine zum höchsten aktuellen Wert hinzuzufügen.
  • Stellen Sie sicher, dass Sie keine Funktion verwenden, die dazu führt, dass ein Index nicht verwendet wird. Wenn die Funktion erforderlich ist, sollten Sie sie zu einem funktionsbasierten Index machen.
  • Denken Sie in Sätzen. Überlegen Sie, ob eine Schleife, in der ein PL / SQL-Block ausgeführt wird, der Aktualisierungen ausführt, als einzelne Aktualisierungsanweisung umgeschrieben werden kann. Wenn nicht, könnte möglicherweise die Massenverarbeitung mit verwendet werden BULK COLLECT ... FORALL.
  • Reduzieren Sie die Arbeit, die zwischen dem ersten UPDATEund dem letzten ausgeführt wird COMMIT. Wenn der Code beispielsweise nach jedem Update eine E-Mail sendet, sollten Sie die E-Mails in die Warteschlange stellen und sie nach dem Festschreiben der Updates senden.
  • Entwerfen Sie die Anwendung für das Warten mit einem SELECT ... FOR UPDATE NOWAIToder WAIT 2. Sie können dann feststellen, dass die Zeile nicht gesperrt werden kann, und den Benutzer darüber informieren, dass in einer anderen Sitzung dieselben Daten geändert werden.
Leigh Riffel
quelle
7

Ich werde eine Antwort aus Entwicklersicht geben.

Meiner Meinung nach liegt es an einem Fehler in Ihrer Anwendung, wenn Sie auf einen Zeilenkonflikt stoßen, wie den, den Sie beschreiben. In den meisten Fällen handelt es sich bei dieser Art von Konflikten um ein Anzeichen für eine Sicherheitsanfälligkeit aufgrund eines verlorenen Updates. Dieser Thread auf AskTom erklärt das Konzept eines verlorenen Updates:

Ein verlorenes Update findet statt, wenn:

Sitzung 1: Lesen Sie Toms Mitarbeiterakte aus

Sitzung 2: Lesen Sie Toms Mitarbeiterakte aus

Sitzung 1: Aktualisierung von Toms Mitarbeiterdatensatz

Sitzung 2: Aktualisierung von Toms Mitarbeiterdatensatz

Sitzung 2 SCHREIBT die Änderungen von Sitzung 1 ÜBER, ohne sie jemals zu sehen - was zu einem verlorenen Update führt.

Sie haben eine böse Nebenwirkung des verlorenen Updates erlebt: Sitzung 2 kann blockiert werden, da Sitzung 1 noch nicht festgeschrieben wurde. Das Hauptproblem ist jedoch, dass Sitzung 2 den Datensatz blind aktualisiert. Angenommen, beide Sitzungen geben die folgende Anweisung aus:

UPDATE table SET col1=:col1, ..., coln=:coln WHERE id = :pk

Nach beiden Anweisungen wurden die Änderungen von Sitzung1 überschrieben, ohne dass Sitzung2 darüber informiert wurde, dass die Zeile von Sitzung 1 geändert wurde.


Verlorene Updates (und der Nebeneffekt von Konflikten) sollten niemals auftreten, sie sind zu 100% vermeidbar. Sie sollten das Sperren verwenden, um dies mit zwei Hauptmethoden zu verhindern: dem optimistischen und dem pessimistischen Sperren .

1) Pessimistisches Sperren

Sie möchten eine Zeile aktualisieren. In diesem Modus verhindern Sie, dass andere Benutzer diese Zeile ändern, indem Sie eine Sperre für diese Zeile anfordern ( SELECT ... FOR UPDATE NOWAITAnweisung). Wenn die Zeile bereits geändert wird, wird eine Fehlermeldung angezeigt, die Sie ordnungsgemäß an den Endbenutzer übertragen können (diese Zeile wird von einem anderen Benutzer geändert). Wenn die Zeile verfügbar ist, nehmen Sie Ihre Änderungen vor (UPDATE) und bestätigen Sie, wann immer Ihre Transaktion abgeschlossen ist.

2) Optimistisches Sperren

Sie möchten eine Zeile aktualisieren. Sie möchten jedoch keine Sperre für diese Zeile aufrechterhalten, weil Sie möglicherweise mehrere Transaktionen zum Aktualisieren der Zeile verwenden (webbasierte Anwendung ohne Status) oder weil Sie nicht möchten, dass ein Benutzer eine Sperre zu lange hält ( Dies kann dazu führen, dass andere Personen blockiert werden. In diesem Fall werden Sie nicht sofort eine Sperre anfordern. Sie werden einen Marker verwenden, um sicherzustellen, dass sich die Zeile nicht geändert hat, wenn Ihr Update veröffentlicht wird. Sie können den Wert aller Spalten zwischenspeichern oder eine Zeitstempelspalte, die automatisch aktualisiert wird, oder eine sequenzbasierte Spalte verwenden. Unabhängig von Ihrer Wahl stellen Sie bei der Durchführung des Updates sicher, dass sich die Markierung in dieser Zeile nicht geändert hat, indem Sie eine Abfrage wie die folgende absenden:

SELECT <...>
  FROM table
 WHERE id = :id
   AND marker = :marker
   FOR UPDATE NOWAIT

Wenn die Abfrage eine Zeile zurückgibt, führen Sie Ihre Aktualisierung durch. Wenn dies nicht der Fall ist, bedeutet dies, dass die Zeile seit Ihrer letzten Abfrage geändert wurde. Sie müssen den Vorgang von Anfang an neu starten.

Hinweis: Wenn Sie allen Anwendungen, die auf Ihre Datenbank zugreifen, vollkommen vertrauen, können Sie sich auf ein direktes Update für das optimistische Sperren verlassen. Sie könnten direkt ausstellen:

UPDATE table
   SET <...>, 
       marker = marker + 1
 WHERE id = :id;

Wenn die Anweisung keine Zeile aktualisiert, wissen Sie, dass jemand diese Zeile geändert hat und Sie müssen von vorne beginnen.

Wenn alle Anwendungen diesem Schema zustimmen, werden Sie niemals von einer anderen Person blockiert und vermeiden das blinde Update. Wenn Sie die Zeile jedoch nicht im Voraus sperren, besteht weiterhin die Gefahr einer unbefristeten Sperre, wenn eine andere Anwendung, ein Stapeljob oder eine direkte Aktualisierung keine optimistische Sperre implementiert. Aus diesem Grund empfehle ich, die Zeile unabhängig von der Auswahl des Sperrschemas immer zu sperren (der Leistungseinbruch kann vernachlässigbar sein, da Sie beim Sperren der Zeile alle Werte einschließlich der Zeilen-ID abrufen).

TL; DR

  • Das Aktualisieren einer Zeile ohne vorherige Sperre setzt die Anwendung einem möglichen "Einfrieren" aus. Dies kann vermieden werden, wenn alle DMLs in der Datenbank optimistische oder pessimistische Sperren implementieren.
  • Stellen Sie sicher, dass die SELECT-Anweisung Werte zurückgibt, die mit früheren SELECT-Anweisungen übereinstimmen (um ein verlorenes Aktualisierungsproblem zu vermeiden).
Vincent Malgrat
quelle
5

Diese Antwort würde wahrscheinlich für einen Eintrag in The Daily WTF qualifizieren.

Richtig, nachdem die Sitzungen verfolgt und gerade USER_SOURCE- ich aufgespürt die Ursache

  • Es überrascht nicht, dass die Ursache eine fehlerhafte Logik war
  • Kürzlich wurde dem SP eine Update-Anweisung hinzugefügt. Die Update-Anweisung würde im Grunde die gesamte Tabelle aktualisieren. Anscheinend hat der betreffende Entwickler vergessen, die where-Klauseln hinzuzufügen, um die erforderlichen Anweisungen zu aktualisieren.
  • Die zu aktualisierende Tabelle war wie oben erwähnt eine der am häufigsten abgewickelten Tabellen und hatte eine große Anzahl von Datensätzen. Das Update würde eine lange, qualvolle Zeit in Anspruch nehmen.
  • Das Ergebnis war, dass andere Sitzungen keine Sperre für den Tisch erhalten konnten und in Zeilensperrkonflikten saßen.
Sathyajith Bhat
quelle