Freigeben über


Vollständige Parallelität

In einer Mehrbenutzerumgebung gibt es zwei Modelle für die Aktualisierung von Daten in einer Datenbank: das Modell der vollständigen Parallelität und das der eingeschränkten Parallelität. Das DataSet-Objekt unterstützt die Nutzung der vollständigen Parallelität für lange Aktivitäten, wie bei der Datenfernverarbeitung und der Interaktion von Benutzern mit Daten.

Die eingeschränkte Parallelität umfasst das Sperren von Zeilen an der Datenquelle, damit Benutzer Daten, die andere Benutzer betreffen, nicht auf irgendeine Weise ändern können. Wenn ein Benutzer bei einem eingeschränkten Modell eine Aktion ausführt, durch die eine Sperre angewendet wird, können andere Benutzer so lange keine mit der Sperre in Konflikt stehenden Aktionen ausführen, bis der Eigentümer der Sperre diese aufgehoben hat. Dieses Modell wird hauptsächlich in Umgebungen verwendet, in denen häufig Datenkonflikte auftreten, oder in denen der Aufwand von Datensperren geringer ist als der Aufwand für Rollbacks von Transaktionen, die aufgrund von Konflikten durch eine Parallelbearbeitung ausgeführt werden müssen.

Daher erstellt ein Benutzer, der eine zu ändernde Zeile abruft, beim Modell der eingeschränkten Parallelität eine Sperre. Solange der Benutzer die Zeile aktualisiert und die Sperre nicht aufgehoben hat, kann die Zeile von keinem anderen Benutzer geändert werden. Aus diesem Grund eignet sich die eingeschränkte Parallelität am besten bei kurzen Sperrfristen wie bei der programmgesteuerten Verarbeitung von Datensätzen. Die eingeschränkte Parallelität ist keine flexible Option, wenn Benutzer mit Daten interagieren und Datensätze dadurch für einen relativ langen Zeitraum sperren.

Im Gegensatz dazu sperren Benutzer bei der vollständigen Parallelität keine Zeile, während sie sie lesen. Wenn ein Benutzer eine Zeile aktualisieren möchte, muss die Anwendung feststellen, ob ein anderer Benutzer die Zeile seit dem Abruf geändert hat. Die vollständige Parallelität wird im Allgemeinen in Umgebungen mit wenigen Datenkonflikten verwendet. Dies 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 beim Modell der vollständigen Parallelität nicht der Fall ist, können Verbindungen zum Server innerhalb eines kürzeren Zeitraums eine Vielzahl von 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.

Die folgenden Tabellen stellen ein Beispiel für eine vollständige Parallelität dar.

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

CustID     LastName     FirstName

101          Smith             Bob

Spaltenname Ursprünglicher Wert Aktueller Wert Wert in Datenbank
CustID 101 101 101
LastName Smith Smith Smith
FirstName Bob Bob Bob

Um 13:01 Uhr ruft Benutzer2 dieselbe Zeile ab.

Um 13:03 Uhr ändert Benutzer2 FirstName von "Bob" in "Robert" und aktualisiert die Datenbank.

Spaltenname Ursprünglicher Wert Aktueller Wert Wert in Datenbank
CustID 101 101 101
LastName Smith Smith Smith
FirstName Bob Robert Bob

Die Aktualisierung ist erfolgreich, weil die Werte in der Datenbank zur Zeit der Aktualisierung mit den ursprünglichen Werten übereinstimmen, die Benutzer2 vorliegen.

Um 13:05 Uhr ändert Benutzer1 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
LastName Smith Smith Smith
FirstName Bob James Robert

Zu diesem Zeitpunkt stellt Benutzer1 eine Verletzung der vollständigen Parallelität fest, weil die Werte in der Datenbank nicht mit den ursprünglichen Werten übereinstimmen, die Benutzer1 erwartet hat. Nun muss entschieden werden, ob die von Benutzer2 vorgenommenen Änderungen mit den Änderungen von Benutzer1 überschrieben oder die Änderungen von Benutzer1 verworfen werden sollen.

Test auf Verletzung der vollständigen Parallelität

Es gibt mehrere Testverfahren, mit denen eine Verletzung der vollständigen Parallelität festgestellt werden kann. Eine Methode besteht in der Aufnahme einer Timestampspalte in die Tabelle. Datenbanken stellen im Allgemeinen Timestampfunktionen zur Verfügung, mit denen der Aktualisierungszeitpunkt (Datum und Uhrzeit) des Datensatzes gekennzeichnet werden kann. Bei dieser Methode wird eine Timestampspalte in die Tabellendefinition eingefügt. Bei jeder Aktualisierung des Datensatzes wird der Timestamp aktualisiert, so dass er den aktuellen Zeitpunkt (Datum und Uhrzeit) angibt. Bei einem Test auf eine Verletzung der vollständigen Parallelität wird die Timestampspalte bei jeder Abfrage des Tabelleninhalts zurückgegeben. Bei einem Aktualisierungsversuch wird der Timestampwert in der Datenbank mit dem ursprünglichen Timestampwert verglichen, der in der geänderten Zeile enthalten ist. Wenn die Werte identisch sind, wird die Timestampspalte mit der aktuellen Uhrzeit aktualisiert, um die Aktualisierung zu kennzeichnen. Wenn die Werte nicht identisch sind, wurde die vollständige Parallelität verletzt.

Beim zweiten Testverfahren, mit dem eine Verletzung der vollständigen Parallelität festgestellt werden kann, wird überprüft, ob alle ursprünglichen Spaltenwerte einer Zeile mit denen in der Datenbank übereinstimmen. Beachten Sie z. B. folgende Abfrage:

SELECT Col1, Col2, Col3 FROM Table1

Sie würden die folgende UPDATE-Anweisung ausgeben, um beim Aktualisieren einer Zeile in Table1 einen Test auf eine Verletzung der vollständigen Parallelität durchzuführen:

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

Solange die ursprünglichen Werte mit den Werten in der Datenbank übereinstimmen, wird die Aktualisierung durchgeführt. Wenn ein Wert geändert wurde, wird die Zeile bei einer Aktualisierung nicht aktualisiert, weil die WHERE-Klausel keine Übereinstimmung findet.

Es empfiehlt sich, stets einen eindeutigen Primärschlüsselwert in einer Abfrage zurückzugeben. Andernfalls könnte die vorhergehende UPDATE-Anweisung mehr als eine Zeile aktualisieren, was möglicherweise nicht von Ihnen beabsichtigt ist.

Wenn in einer Spalte Ihrer Datenquelle NULL-Werte zulässig sind, müssen Sie Ihre WHERE-Klausel erweitern, um nach einem entsprechenden Nullverweis in Ihrer lokalen Tabelle und in der Datenquelle zu suchen. Die folgende UPDATE-Anweisung prüft z. B., ob ein Nullverweis in der lokalen Zeile noch mit einem Nullverweis in der Datenquelle übereinstimmt oder ob der Wert in der lokalen Zeile 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 bei einem Modell der vollständigen Parallelität auch weniger strenge Kriterien anwenden. 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, so dass die Daten überschrieben werden, sofern nicht bestimmte Felder seit deren letzten Abfrage aktualisiert wurden.

Das DataAdapter.RowUpdated-Ereignis

Das DataAdapter.RowUpdated-Ereignis kann in Verbindung mit den oben beschriebenen Methoden eingesetzt werden, um Ihre Anwendung auf eine Verletzung der vollständigen Parallelität hinzuweisen. RowUpdated 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 Zeilen zurück, die von einem bestimmten Aktualisierungsbefehl für eine bestimmte geänderte Zeile in einer Tabelle betroffen sind. Wenn Sie den Aktualisierungsbefehl auf einen Test auf vollständige Parallelität festlegen, gibt die RecordsAffected-Eigenschaft bei einer Verletzung der vollständigen Parallelität den Wert 0 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 über das RowUpdated-Ereignis finden Sie unter Arbeiten mit DataAdapter-Ereignissen.

Optional können Sie DataAdapter.ContinueUpdateOnError auf True setzen, bevor Sie Update aufrufen und auf die in der RowError-Eigenschaft einer bestimmten Zeile gespeicherten Fehlerinformationen reagieren, wenn die Update-Operation beendet ist. Weitere Informationen finden Sie unter Hinzufügen und Lesen von Informationen zu Zeilenfehlern.

Beispiel für vollständige Parallelität

Im folgenden einfachen Beispiel wird der UpdateCommand eines DataAdapter darauf festgelegt, einen Test auf vollständige Parallelität auszuführen; außerdem wird mit dem RowUpdated-Ereignis getestet, ob die vollständige Parallelität verletzt wurde. Wenn die vollständige Parallelität verletzt wurde, legt die Anwendung den RowError der Zeile fest, für die die Aktualisierung angegeben wurde, um eine Verletzung der vollständigen Parallelität anzugeben.

Beachten Sie, dass die Parameterwerte, die an die WHERE-Klausel des UPDATE-Befehls übergeben wurden, den Original-Werten der entsprechenden Spalten zugeordnet werden.

  Dim nwindConn As SqlConnection = New SqlConnection("Data Source=localhost;Integrated Security=SSPI;Initial Catalog=northwind")

  Dim custDA As SqlDataAdapter = New SqlDataAdapter("SELECT CustomerID, CompanyName FROM Customers ORDER BY CustomerID", nwindConn)

  ' The Update command checks for optimistic concurrency violations in the WHERE clause.
  custDA.UpdateCommand = New SqlCommand("UPDATE Customers (CustomerID, CompanyName) VALUES(@CustomerID, @CompanyName) " & _
                                        "WHERE CustomerID = @oldCustomerID AND CompanyName = @oldCompanyName", nwindConn)
  custDA.UpdateCommand.Parameters.Add("@CustomerID", SqlDbType.NChar, 5, "CustomerID")
  custDA.UpdateCommand.Parameters.Add("@CompanyName", SqlDbType.NVarChar, 30, "CompanyName")

  ' Pass the original values to the WHERE clause parameters.
  Dim myParm As SqlParameter
  myParm = custDA.UpdateCommand.Parameters.Add("@oldCustomerID", SqlDbType.NChar, 5, "CustomerID")
  myParm.SourceVersion = DataRowVersion.Original
  myParm = custDA.UpdateCommand.Parameters.Add("@oldCompanyName", SqlDbType.NVarChar, 30, "CompanyName")
  myParm.SourceVersion = DataRowVersion.Original

  ' Add the RowUpdated event handler.
  AddHandler custDA.RowUpdated, New SqlRowUpdatedEventHandler(AddressOf OnRowUpdated)

  Dim custDS As DataSet = New DataSet()
  custDA.Fill(custDS, "Customers")

  ' Modify the DataSet contents.

  custDA.Update(custDS, "Customers")

  Dim myRow As DataRow

  For Each myRow In custDS.Tables("Customers").Rows
    If myRow.HasErrors Then Console.WriteLine(myRow(0) & vbCrLf & myRow.RowError)
  Next


Private Shared Sub OnRowUpdated(sender As object, args As SqlRowUpdatedEventArgs)
  If args.RecordsAffected = 0
    args.Row.RowError = "Optimistic Concurrency Violation Encountered"
    args.Status = UpdateStatus.SkipCurrentRow
  End If
End Sub
[C#]
  SqlConnection nwindConn = new SqlConnection("Data Source=localhost;Integrated Security=SSPI;Initial Catalog=northwind");

  SqlDataAdapter custDA = new SqlDataAdapter("SELECT CustomerID, CompanyName FROM Customers ORDER BY CustomerID", nwindConn);

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

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

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

  DataSet custDS = new DataSet();
  custDA.Fill(custDS, "Customers");

  // Modify the DataSet contents.

  custDA.Update(custDS, "Customers");

  foreach (DataRow myRow in custDS.Tables["Customers"].Rows)
  {
    if (myRow.HasErrors)
      Console.WriteLine(myRow[0] + "\n" + myRow.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

Beispielszenarien zu ADO.NET | Aktualisieren der Datenbank mit einem DataAdapter und dem DataSet | Arbeiten mit DataAdapter-Ereignissen | Hinzufügen und Lesen von Informationen zu Zeilenfehlern | Zugreifen auf Daten mit ADO.NET | Datenzugriff mit .NET Framework-Datenprovidern