Click here to view and discuss this page in DocCommentXchange. In the future, you will be sent there automatically.

SQL Anywhere 11.0.1 (Deutsch) » SQL Anywhere Server - Programmierung » Einführung in die Programmierung mit SQL Anywhere » SQL in Anwendungen verwenden » SQL Anywhere-Cursor » Cursor-Empfindlichkeit und Performance

 

Verlorene Aktualisierungen

Bei der Verwendung von aktualisierbaren Cursor muss darauf geachtet werden, dass keine Aktualisierungen verloren gehen. Eine verlorene Aktualisierung liegt vor, wenn zwei (oder mehr) Transaktionen dieselbe Zeile aktualisieren, aber keine der beiden Transaktionen von der Änderung durch die andere Transaktion weiß und daher die erste Änderung durch die zweite Änderung überschrieben wird. Dieses Problem wird im folgenden Beispiel erläutert:

  1. Eine Anwendung öffnet in der Beispieldatenbank in der folgenden Abfrage einen Cursor.

    SELECT ID, Quantity
    FROM Products;
    ID Quantity
    300 28
    301 54
    302 75
    ... ...
  2. Die Anwendung ruft die Zeile mit ID = 300 durch den Cursor ab.

  3. Eine weitere Transaktion aktualisiert die Zeile mit der folgenden Anweisung:

    UPDATE Products
    SET Quantity = Quantity - 10
    WHERE ID = 300;
  4. Die Anwendung aktualisiert dann die Zeile durch den Cursor auf einen Wert von (Quantity - 5 ).

  5. Der korrekte Endwert für die Zeile sollte 13 sein. Wenn der Cursor die Zeile vorab abgerufen hätte, würde der neue Wert für die Zeile 23 lauten und die Aktualisierung der anderen Transaktion verloren gehen.

In einer Datenbankanwendung gibt es auf jeder Ebene das Potenzial für eine verlorene Aktualisierung, wenn eine Zeile geändert wird, ohne zuvor ihren Wert zu überprüfen. Auf höheren Isolationsstufen (2 und 3), können Sperren (Lese-, Absichts- und Schreibsperren) verwendet werden, um sicherzustellen, dass eine Zeile nicht geändert werden kann, wenn die Zeilen von der Anwendung gelesen wurde. Auf den Isolationstufen 0 und 1 ist das Potenzial für einen Verlust von Aktualisierungen dagegen höher. Auf der Isolationsebene 0 werden keine Lesesperren erworben, um nachfolgende Änderungen der Daten zu verhindern, und die Isolationsebene 1 sperrt nur die aktuelle Zeile. Bei der Verwendung von Snapshot-Isloation kann es nicht zu verlorenen Aktualisierungen kommen, da jeder Versuch, den alten Wert zu ändern, zu einem Aktualisierungskonflikt führt. Die Verwendung von Prefetch-Vorgängen auf Isolationsstufe 1 kann auch zu einem möglichen Verlust von Aktualisierungen führen, da die Ergebnismengenzeile, auf die die Anwendung positioniert ist und die sich im Prefetch-Puffer des Clients befindet, möglicherweise nicht dieselbe ist wie die aktuelle Zeile, auf die der Server im Cursor positioniert ist.

Um einen Aktualisierungsverlust auf Isolationsstufe 1 zu verhindern, unterstützt der Datenbankserver drei verschiedene Parallelitäts-Kontrollmechanismen, die von einer Anwendung festgelegt werden können:

  1. Der Erwerb von Absichtszeilensperren für jede Zeile im Cursor, wenn er abgerufen wird. Absichtssperren verhindern, dass andere Transaktionen Absichts- oder Schreibsperren für dieselbe Zeile erwerben, und damit auch gleichzeitige Aktualisierungen. Absichtssperren blockieren jedoch keine Lesezeilensperren und haben damit keine Auswirkung auf die Parallelität reiner Leseanweisungen.

  2. Die Verwendung eines wertempfindlichen Cursors. Wertempfindliche Cursor können für die Protokollierung verwendet werden, wenn eine zugrunde liegende Zeile geändert oder gelöscht wurde, sodass die Anwendung antworten kann.

  3. Die Verwendung von FETCH FOR UPDATE, womit eine Absichtszeilensperre für die betreffende Zeile erworben wird.

Wie diese Alternativen angegeben werden, hängt von der Schnittstelle ab, die von der Anwendung verwendet wird. Für die ersten beiden Alternativen, die sich auf eine SELECT-Anweisung beziehen, gibt es folgende Möglichkeiten:

  • In ODBC können keine Aktualisierungsverluste auftreten, da die Anwendung bei der Deklaration eines aktualisierbaren Cursors einen Cursor-Parallelitätsparameter für die Funktion SQLSetStmtAttr angeben muss. Bei diesem Parameter handelt es sich um SQL_CONCUR_LOCK, SQL_CONCUR_VALUES, SQL CONCUR_READ_ONLY oder SQL_CONCUR_TIMESTAMP. Bei SQL_CONCUR_LOCK erwirbt der Datenbankserver Absichtszeilensperren. Bei SQL_CONCUR_VALUES und SQL_CONCUR_TIMESTAMP wird ein wertempfindlicher Cursor verwendet. SQL_CONCUR_READ_ONLY wird für schreibgeschützte Cursor verwendet und ist die Standardeinstellung.

  • In JDBC ist die Parallelitätseinstellung für eine Anweisung ähnlich wie bei ODBC. Der iAnywhere JDBC-Treiber unterstützt die JDBC-Parallelitätswerte RESULTSET_CONCUR_READ_ONLY und RESULTSET_CONCUR_UPDATABLE. Der erste Wert entspricht der ODBC-Parallelitätseinstellung SQL_CONCUR_READ_ONLY und legt eine reine Leseanweisung fest. Der zweite Wert entspricht der Einstellung ODBC SQL_CONCUR_LOCK, sodass Absichtszeilensperren verwendet werden, um einen Verlust von Aktualisierungen zu verhindern. Beachten Sie, dass wertempfindliche Cursor in der JDBC 3.0-Spezifikation nicht direkt angegeben werden können.

  • In jConnect werden aktualisierbare Cursor auf der API-Ebene unterstützt, doch die zu Grunde liegende Implementierung (unter Verwendung von TDS) unterstützt keine Aktualisierungen durch einen Cursor. Stattdessen sendet jConnect eine eigene UPDATE-Anweisung an den Datenbankserver, um die angegebene Zeile zu aktualisieren. Um einen Aktualisierungsverlust zu vermeiden, muss die Anwendung auf Isolationsstufe 2 oder höher ausgeführt werden. Alternativ dazu kann die Anwendung separate UPDATE-Anweisungen vom Cursor ausführen. Es muss jedoch sichergestellt werden, dass die UPDATE-Anweisung überprüft, ob die Zeilenwerte geändert wurden, seit die Zeile gelesen wurde, indem entsprechende Bedingungen in die WHERE-Klauseln der UPDATE-Anweisung positioniert werden.

  • In Embedded SQL kann eine Parallelitätsangabe gesetzt werden, indem Syntax in die SELECT-Anweisung selbst oder in die Cursordeklaration aufgenommen wird. In der SELECT-Anweisung bewirkt die Syntax SELECT ... FOR UPDATE BY LOCK, dass der Datenbankserver eine Absichtszeilensperre für die Ergebnismenge erwirbt.

    SELECT ... FOR UPDATE BY [ VALUES | TIMESTAMP ] verursacht dagegen, dass der Datenbankserver den Cursortyp in einen wertempfindlichen Cursor ändert. Wenn eine bestimmte Zeile seit dem letzten Lesen durch den Cursor geändert wurde, erhält die Anwendung eine Warnung (SQLE_ROW_UPDATED_WARNING) für eine FETCH-Anweisung bzw. einen Fehler (SQLE_ROW_UPDATED_SINCE_READ) für eine UPDATE WHERE CURRENT OF-Anweisung. Wenn die Zeile gelöscht wurde, erhält die Anwendung ebenfalls einen Fehler (SQLE_NO_CURRENT_ROW).

Die FETCH FOR UPDATE-Funktionalität wird ebenfalls von der Embedded SQL- und der ODBC-Schnittstelle unterstützt, wenngleich die Details abhängig von der verwendeten API-Schnittstelle unterschiedlich sind.

In Embedded SQL verwendet die Anwendung FETCH FOR UPDATE anstelle von FETCH, damit eine Absichtssperre für die Zeile erworben wird. In ODBC verwendet die Anwendung den API-Aufruf SQLSetPos mit dem Vorgangsargument SQL_POSITION oder SQL_REFRESH und dem Sperrentypargument SQL_LOCK_EXCLUSIVE, um eine Absichtssperre für eine Zeile zu erwerben. In SQL Anywhere sind dies langfristige Sperren, die gehalten werden, bis die Transaktion festgeschrieben oder zurückgesetzt wird.