Optimistische Parallelität

Gilt für: .NET Framework .NET .NET Standard

Herunterladen von ADO.NET

In einer Umgebung mit mehreren Benutzern gibt es zwei Modelle für das Update von Daten in einer Datenbank: das Modell der vollständigen Parallelität und das Modell der eingeschränkten Parallelität. Das DataSet-Objekt ist darauf ausgelegt, die Verwendung optimistischer Parallelitätssteuerung bei lang andauernden Vorgängen wie dem Remotezugriff auf Daten und dem Arbeiten mit Daten zu fördern.

Die pessimistische Nebenläufigkeitssteuerung beinhaltet das Sperren von Zeilen in der Datenquelle, um zu verhindern, dass andere Benutzer Daten auf eine Weise ändern, die sich auf den aktuellen Benutzer auswirkt. In einem pessimistischen Modell können andere Benutzer, wenn ein Benutzer eine Aktion ausführt, die dazu führt, dass eine Sperre gesetzt wird, keine Aktionen ausführen, die mit der Sperre in Konflikt stehen würden, bis der Inhaber der Sperre sie freigibt. Dieses Modell wird hauptsächlich in Umgebungen verwendet, in denen eine starke Konkurrenz um Daten besteht, sodass der Aufwand, Daten durch Sperren zu schützen, geringer ist als der Aufwand, Transaktionen zurückzusetzen, wenn Konflikte bei gleichzeitigen Zugriffen auftreten.

Daher erstellt ein Benutzer, der eine Zeile aktualisiert, bei einem Modell für pessimistische Parallelität eine Sperre. Solange der Benutzer die Aktualisierung nicht abgeschlossen und die Sperre nicht aufgehoben hat, kann niemand sonst diese Zeile ändern. Aus diesem Grund lässt sich pessimistische Nebenläufigkeit am besten dann einsetzen, wenn die Sperrzeiten kurz sind, wie etwa bei der programmgesteuerten Verarbeitung von Datensätzen. Pessimistische Nebenläufigkeit ist keine skalierbare Option, wenn Benutzer mit Daten interagieren und dadurch Datensätze für relativ lange Zeiträume gesperrt werden.

Hinweis

Wenn Sie mehrere Zeilen im selben Vorgang aktualisieren müssen, ist das Erstellen einer Transaktion eine besser skalierbare Option als pessimistisches Sperren.

Im Gegensatz dazu sperren Benutzer, die optimistische Parallelitätssteuerung verwenden, eine Zeile nicht, wenn sie sie lesen. Wenn ein Benutzer eine Zeile aktualisieren möchte, muss durch die Anwendung festgestellt werden, ob ein anderer Benutzer die Zeile seit dem Abruf geändert hat. Optimistische Parallelität wird im Allgemeinen in Umgebungen mit geringer Datenkonkurrenz verwendet. Vollständige Parallelität führt zu einer Leistungssteigerung, weil keine Datensätze gesperrt werden müssen, denn für das Sperren von Datensätzen sind zusätzliche Serverressourcen erforderlich. Außerdem ist für die Verwaltung von Datensatzsperren eine ständige Verbindung zum Datenbankserver notwendig. Da dies bei einem optimistischen Nebenläufigkeitsmodell nicht der Fall ist, können Verbindungen zum Server in kürzerer Zeit mehr Clients bedienen.

Beim Modell der vollständigen Parallelität wird dann von einer Verletzung ausgegangen, wenn, nachdem ein Benutzer einen Wert aus der Datenbank empfangen hat, ein anderer Benutzer den Wert ändert, bevor der erste Benutzer den Wert zu ändern versucht hat. Wie der Server eine Parallelitätsverletzung auflöst, kann am Besten anhand des folgenden Beispiels erläutert werden.

Die folgenden Tabellen zeigen ein Beispiel für optimistische Nebenläufigkeit.

Um 13:00 Uhr ruft User1 eine Zeile mit den folgenden Werten aus der Datenbank auf:

CustID Nachname Vorname

101 Smith Bob

Spaltenname Ursprünglicher Wert Aktueller Wert Wert in Datenbank
CustID 101 101 101
Nachname Smith Smith Smith
Vorname Bob Bob Bob

Um 13:01 Uhr ruft User2 dieselbe Zeile ab.

User2 ändert FirstName um 13:03 Uhr von „Bob“ in „Robert“ und aktualisiert die Datenbank.

Spaltenname Ursprünglicher Wert Aktueller Wert Wert in Datenbank
CustID 101 101 101
Nachname Smith Smith Smith
Vorname Bob Robert Bob

Das Update ist erfolgreich, weil die Werte in der Datenbank zur Zeit des Updates mit den ursprünglichen Werten übereinstimmen, die User2 vorliegen.

Um 13:05 Uhr ändert User1 den Vornamen von „Bob“ in „James“ und versucht, die Zeile zu aktualisieren.

Spaltenname Ursprünglicher Wert Aktueller Wert Wert in Datenbank
CustID 101 101 101
Nachname Smith Smith Smith
Vorname Bob James Robert

Zu diesem Zeitpunkt stellt User1 eine Verletzung der optimistischen Parallelität fest, weil der Wert in der Datenbank ("Robert") nicht mit dem ursprünglichen Wert übereinstimmt, den User1 erwartet hat ("Bob"). Die Parallelitätsverletzung sagt Ihnen einfach nur, dass das Update fehlgeschlagen ist. Nun muss entschieden werden, ob die von User2 vorgenommenen Änderungen mit den Änderungen von User1 überschrieben oder die Änderungen von User1 verworfen werden sollen.

Testen auf Verstöße gegen die optimistische Nebenläufigkeit

Es gibt mehrere Verfahren, um auf eine Verletzung der optimistischen Nebenläufigkeit zu testen. Eine Methode besteht im Einfügen einer Timestamp-Spalte in die Tabelle.

Datenbanken stellen im Allgemeinen Timestamp-Funktionen zur Verfügung, mit denen der Updatezeitpunkt (Datum und Uhrzeit) des Datensatzes gekennzeichnet werden kann. Bei dieser Methode wird eine Timestamp-Spalte in die Tabellendefinition eingefügt. Bei jedem Update des Datensatzes wird der Timestamp aktualisiert, sodass er den aktuellen Zeitpunkt (Datum und Uhrzeit) angibt.

Bei einem Test auf Verstöße gegen die optimistische Parallelität wird die Zeitstempelspalte bei jeder Abfrage des Inhalts der Tabelle zurückgegeben. Bei einem Updateversuch wird der Timestamp-Wert in der Datenbank mit dem ursprünglichen Timestamp-Wert verglichen, der in der geänderten Zeile enthalten ist. Wenn die Werte identisch sind, wird die Timestamp-Spalte mit der aktuellen Uhrzeit aktualisiert, um das Update zu kennzeichnen. Wenn die Werte nicht identisch sind, wurde die vollständige Parallelität verletzt.

Eine weitere Technik zum Prüfen auf eine Verletzung der optimistischen Nebenläufigkeit besteht darin, zu überprüfen, ob alle ursprünglichen Spaltenwerte in einer Zeile noch mit den in der Datenbank gefundenen Werten übereinstimmen. Betrachten Sie beispielsweise die folgende Abfrage:

SELECT Col1, Col2, Col3 FROM Table1  

Um beim Aktualisieren einer Zeile in Tabelle1 auf eine optimistische Parallelitätsverletzung zu testen, würden Sie die folgende UPDATE Anweisung ausgeben:

UPDATE Table1 Set Col1 = @NewCol1Value,  
              Set Col2 = @NewCol2Value,  
              Set Col3 = @NewCol3Value  
WHERE Col1 = @OldCol1Value AND  
      Col2 = @OldCol2Value AND  
      Col3 = @OldCol3Value  

Wenn die ursprünglichen Werte mit den Werten in der Datenbank übereinstimmen, wird das Update durchgeführt. Wenn ein Wert geändert wurde, wird die Zeile von der UPDATE-Anweisung nicht aktualisiert, weil die WHERE-Klausel keine Übereinstimmung findet.

Es empfiehlt sich, in einer Abfrage stets einen eindeutigen Primärschlüsselwert zurückzugeben. Andernfalls kann die vorangehende UPDATE Anweisung mehr als eine Zeile aktualisieren, was möglicherweise nicht Ihre Absicht ist.

Wenn in einer Spalte Ihrer Datenquelle NULL-Werte zulässig sind, müssen Sie Ihre WHERE-Klausel erweitern, um nach einem entsprechenden NULL-Verweis in Ihrer lokalen Tabelle und in der Datenquelle zu suchen. Mit der folgenden UPDATE Anweisung wird beispielsweise überprüft, ob ein Nullverweis in der lokalen Zeile immer noch mit einem Nullverweis an der Datenquelle übereinstimmt oder dass der Wert in der lokalen Zeile immer noch mit dem Wert in der Datenquelle übereinstimmt.

UPDATE Table1 Set Col1 = @NewVal1  
  WHERE (@OldVal1 IS NULL AND Col1 IS NULL) OR Col1 = @OldVal1  

Sie können auch weniger strenge Kriterien anwenden, wenn Sie ein optimistisches Nebenläufigkeitsmodell verwenden. Wenn Sie z. B. nur die Primärschlüsselspalten in der WHERE-Klausel verwenden, werden die Daten unabhängig davon überschrieben, ob die anderen Spalten seit der letzten Abfrage aktualisiert wurden oder nicht. Zudem besteht die Möglichkeit, eine WHERE-Klausel auf spezifische Spalten anzuwenden, sodass die Daten überschrieben werden, sofern nicht bestimmte Felder seit deren letzten Abfrage aktualisiert wurden.

DataAdapter.RowUpdated-Ereignis

Das RowUpdated-Ereignis des DataAdapter-Objekts kann in Verbindung mit den oben beschriebenen Methoden eingesetzt werden, um Ihre Anwendung über Verstöße gegen die optimistische Nebenläufigkeit zu benachrichtigen. Das RowUpdated-Ereignis tritt nach jedem Versuch auf, eine Modified-Zeile eines DataSets zu aktualisieren. Damit können Sie spezifischen Behandlungscode hinzufügen, einschließlich Verarbeitung bei Ausnahmen, Einfügen von benutzerdefinierten Fehlerinformationen, Hinzufügen einer Wiederholungslogik usw.

Das RowUpdatedEventArgs-Objekt gibt eine RecordsAffected-Eigenschaft mit der Anzahl der Zeilen zurück, die von einem bestimmten Updatebefehl für eine bestimmte geänderte Zeile in einer Tabelle betroffen sind. Wenn Sie den Updatebefehl auf einen Test auf optimistische Nebenläufigkeit festlegen, gibt die RecordsAffected-Eigenschaft bei einem Verstoß der optimistischen Nebenläufigkeit den Wert „0“ (null) als Ergebnis zurück, weil keine Datensätze aktualisiert wurden. Wenn dies der Fall ist, wird eine Ausnahme ausgelöst.

Mit dem RowUpdated-Ereignis können Sie dieser Situation begegnen und die Ausnahme verhindern, indem Sie einen entsprechenden RowUpdatedEventArgs.Status-Wert festlegen, z. B. UpdateStatus.SkipCurrentRow. Weitere Informationen zum RowUpdated-Ereignis finden Sie unter Umgang mit DataAdapter-Ereignissen.

Optional können Sie DataAdapter.ContinueUpdateOnError auf TRUE festlegen, bevor Sie Update aufrufen, und auf die in der RowError-Eigenschaft einer bestimmten Zeile gespeicherten Fehlerinformationen reagieren, wenn die Update-Operation abgeschlossen ist. Weitere Informationen finden Sie unter Informationen zu Zeilenfehlern.

Beispiel für die optimistische Nebenläufigkeit

Das folgende einfache Beispiel legt den UpdateCommand eines DataAdapter so fest, dass auf optimistische Parallelitätssteuerung geprüft wird, und verwendet anschließend das RowUpdated-Ereignis, um Verstöße gegen die optimistische Parallelitätssteuerung zu überprüfen. Wenn ein Verstoß gegen die optimistische Nebenläufigkeit ermittelt wird, legt die Anwendung die RowError-Eigenschaft der Zeile fest, für die das Update ausgeführt wurde, um einen Verstoß gegen die optimistische Nebenläufigkeit anzugeben.

Beachten Sie, dass die parameterwerte, die an die WHERE-Klausel des UPDATE Befehls übergeben werden, den Originalwerten ihrer jeweiligen Spalten zugeordnet sind.

using System;
using System.Data;
using Microsoft.Data.SqlClient;

class Program
{
    static void Main(string[] args)
    {
        string connectionString = "Data Source = localhost; Integrated Security = true; Initial Catalog = Northwind";

        using (SqlConnection connection = new SqlConnection(connectionString))
        {
            // Assumes connection is a valid SqlConnection.  
            SqlDataAdapter adapter = new SqlDataAdapter(
              "SELECT CustomerID, CompanyName FROM Customers ORDER BY CustomerID",
              connection);

            // The Update command checks for optimistic concurrency violations  
            // in the WHERE clause.  
            adapter.UpdateCommand = new SqlCommand("UPDATE Customers Set CustomerID = @CustomerID, CompanyName = @CompanyName " +
               "WHERE CustomerID = @oldCustomerID AND CompanyName = @oldCompanyName", connection);
            adapter.UpdateCommand.Parameters.Add(
              "@CustomerID", SqlDbType.NChar, 5, "CustomerID");
            adapter.UpdateCommand.Parameters.Add(
              "@CompanyName", SqlDbType.NVarChar, 30, "CompanyName");

            // Pass the original values to the WHERE clause parameters.  
            SqlParameter parameter = adapter.UpdateCommand.Parameters.Add(
              "@oldCustomerID", SqlDbType.NChar, 5, "CustomerID");
            parameter.SourceVersion = DataRowVersion.Original;
            parameter = adapter.UpdateCommand.Parameters.Add(
              "@oldCompanyName", SqlDbType.NVarChar, 30, "CompanyName");
            parameter.SourceVersion = DataRowVersion.Original;

            // Add the RowUpdated event handler.  
            adapter.RowUpdated += new SqlRowUpdatedEventHandler(OnRowUpdated);

            DataSet dataSet = new DataSet();
            adapter.Fill(dataSet, "Customers");

            // Modify the DataSet contents.
            adapter.Update(dataSet, "Customers");

            foreach (DataRow dataRow in dataSet.Tables["Customers"].Rows)
            {
                if (dataRow.HasErrors)
                    Console.WriteLine(dataRow[0] + "\n" + dataRow.RowError);
            }
        }
    }

    protected static void OnRowUpdated(object sender, SqlRowUpdatedEventArgs args)
    {
        if (args.RecordsAffected == 0)
        {
            args.Row.RowError = "Optimistic Concurrency Violation Encountered";
            args.Status = UpdateStatus.SkipCurrentRow;
        }
    }
}

Siehe auch: