Delen via


Optimistische gelijktijdige uitvoering

In een omgeving met meerdere gebruikers zijn er twee modellen voor het bijwerken van gegevens in een database: optimistische gelijktijdigheid en pessimistische gelijktijdigheid. Het DataSet object is ontworpen om het gebruik van optimistische gelijktijdigheid voor langdurige activiteiten aan te moedigen, zoals externe gegevens en interactie met gegevens.

Pessimistische gelijktijdigheid omvat het vergrendelen van rijen in de gegevensbron om te voorkomen dat andere gebruikers gegevens wijzigen op een manier die van invloed is op de huidige gebruiker. Wanneer een gebruiker in een pessimistisch model een actie uitvoert die ervoor zorgt dat een vergrendeling wordt toegepast, kunnen andere gebruikers geen acties uitvoeren die conflicteren met de vergrendeling totdat de eigenaar van de vergrendeling deze vrijgeeft. Dit model wordt voornamelijk gebruikt in omgevingen waarbij er veel conflicten zijn voor gegevens, zodat de kosten voor het beveiligen van gegevens met vergrendelingen lager zijn dan de kosten voor het terugdraaien van transacties als er gelijktijdigheidsconflicten optreden.

Daarom wordt in een pessimistisch gelijktijdigheidsmodel een gebruiker die een rij bijwerken een vergrendeling tot stand brengt. Totdat de gebruiker de update heeft voltooid en de vergrendeling heeft vrijgegeven, kan niemand anders die rij wijzigen. Daarom wordt pessimistische gelijktijdigheid het beste geïmplementeerd wanneer vergrendelingstijden kort zijn, zoals in programmatische verwerking van records. Pessimistische gelijktijdigheid is geen schaalbare optie wanneer gebruikers interactie hebben met gegevens en ervoor zorgen dat records gedurende relatief lange tijd worden vergrendeld.

Notitie

Als u meerdere rijen in dezelfde bewerking wilt bijwerken, is het maken van een transactie een meer schaalbare optie dan het gebruik van pessimistische vergrendeling.

Gebruikers die optimistische gelijktijdigheid gebruiken, vergrendelen daarentegen geen rij wanneer ze deze lezen. Wanneer een gebruiker een rij wil bijwerken, moet de toepassing bepalen of een andere gebruiker de rij heeft gewijzigd sinds deze is gelezen. Optimistische gelijktijdigheid wordt over het algemeen gebruikt in omgevingen met een lage conflicten voor gegevens. Optimistische gelijktijdigheid verbetert de prestaties omdat er geen vergrendeling van records vereist is en het vergrendelen van records extra serverbronnen vereist. Voor het onderhouden van recordvergrendelingen is ook een permanente verbinding met de databaseserver vereist. Omdat dit niet het geval is in een optimistisch gelijktijdigheidsmodel, zijn verbindingen met de server vrij om een groter aantal clients in minder tijd te bedienen.

In een optimistisch gelijktijdigheidsmodel wordt een schending beschouwd als er, nadat een gebruiker een waarde van de database heeft ontvangen, een andere gebruiker de waarde wijzigt voordat de eerste gebruiker deze heeft gewijzigd. Hoe de server een schending van gelijktijdigheid oplost, wordt het beste weergegeven door eerst het volgende voorbeeld te beschrijven.

De volgende tabellen volgen een voorbeeld van optimistische gelijktijdigheid.

Om 13:00 uur leest User1 een rij uit de database met de volgende waarden:

CustID LastName FirstName

101 Smith Bob

Kolomnaam Oorspronkelijke waarde Huidige waarde Waarde in database
CustID 101 101 101
LastName Smith Smith Smith
FirstName Bob Bob Bob

Om 13:01 uur leest Gebruiker2 dezelfde rij voor.

Om 13:03 uur wijzigt User2 FirstName van 'Bob' in 'Robert' en werkt de database bij.

Kolomnaam Oorspronkelijke waarde Huidige waarde Waarde in database
CustID 101 101 101
LastName Smith Smith Smith
FirstName Bob Robert Bob

De update slaagt omdat de waarden in de database op het moment van bijwerken overeenkomen met de oorspronkelijke waarden die Gebruiker2 heeft.

Om 13:05 uur wijzigt User1 de voornaam van Bob in 'James' en probeert de rij bij te werken.

Kolomnaam Oorspronkelijke waarde Huidige waarde Waarde in database
CustID 101 101 101
LastName Smith Smith Smith
FirstName Bob James Robert

Op dit moment ondervindt Gebruiker1 een optimistische gelijktijdigheidsschending omdat de waarde in de database ('Robert') niet meer overeenkomt met de oorspronkelijke waarde die Gebruiker1 verwachtte ('Bob'). De schending van gelijktijdigheid laat u gewoon weten dat de update is mislukt. De beslissing moet nu worden genomen of de wijzigingen die door Gebruiker2 zijn opgegeven, moeten worden overschreven met de wijzigingen die door Gebruiker1 zijn opgegeven of om de wijzigingen door Gebruiker1 te annuleren.

Testen op optimistische gelijktijdigheidsschendingen

Er zijn verschillende technieken voor het testen op een optimistische gelijktijdigheidsschending. Een van de twee omvat het opnemen van een tijdstempelkolom in de tabel. Databases bieden vaak tijdstempelfunctionaliteit die kan worden gebruikt om de datum en tijd te identificeren waarop de record voor het laatst is bijgewerkt. Met deze techniek wordt een tijdstempelkolom opgenomen in de tabeldefinitie. Wanneer de record wordt bijgewerkt, wordt de tijdstempel bijgewerkt met de huidige datum en tijd. In een test voor optimistische gelijktijdigheidsschendingen wordt de tijdstempelkolom geretourneerd met een query van de inhoud van de tabel. Wanneer een update wordt uitgevoerd, wordt de tijdstempelwaarde in de database vergeleken met de oorspronkelijke tijdstempelwaarde in de gewijzigde rij. Als deze overeenkomen, wordt de update uitgevoerd en wordt de tijdstempelkolom bijgewerkt met de huidige tijd om de update weer te geven. Als ze niet overeenkomen, is er een optimistische gelijktijdigheidsschending opgetreden.

Een andere techniek voor het testen van een optimistische gelijktijdigheidsschending is om te controleren of alle oorspronkelijke kolomwaarden in een rij nog steeds overeenkomen met de waarden in de database. Kijk bijvoorbeeld eens naar de volgende query:

SELECT Col1, Col2, Col3 FROM Table1  

Als u wilt testen op een optimistische gelijktijdigheidsfout bij het bijwerken van een rij in Tabel1, geeft u de volgende UPDATE-instructie uit:

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

Zolang de oorspronkelijke waarden overeenkomen met de waarden in de database, wordt de update uitgevoerd. Als een waarde is gewijzigd, wijzigt de update de rij niet omdat de WHERE-component geen overeenkomst vindt.

Houd er rekening mee dat het wordt aanbevolen om altijd een unieke primaire-sleutelwaarde in uw query te retourneren. Anders kan de voorgaande UPDATE-instructie meer dan één rij bijwerken, wat mogelijk niet uw intentie is.

Als een kolom in uw gegevensbron null-waarden toestaat, moet u mogelijk uw WHERE-component uitbreiden om te controleren op een overeenkomende null-verwijzing in uw lokale tabel en in de gegevensbron. Met de volgende UPDATE-instructie wordt bijvoorbeeld gecontroleerd of een null-verwijzing in de lokale rij nog steeds overeenkomt met een null-verwijzing in de gegevensbron of dat de waarde in de lokale rij nog steeds overeenkomt met de waarde in de gegevensbron.

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

U kunt er ook voor kiezen om minder beperkende criteria toe te passen bij het gebruik van een optimistisch gelijktijdigheidsmodel. Als u bijvoorbeeld alleen de primaire-sleutelkolommen in de WHERE-component gebruikt, worden de gegevens overschreven, ongeacht of de andere kolommen sinds de laatste query zijn bijgewerkt. U kunt ook een WHERE-component alleen toepassen op specifieke kolommen, wat resulteert in het overschrijven van gegevens, tenzij bepaalde velden zijn bijgewerkt sinds ze voor het laatst zijn opgevraagd.

De gebeurtenis DataAdapter.RowUpdated

De RowUpdated-gebeurtenis van het DataAdapter object kan worden gebruikt in combinatie met de technieken die eerder zijn beschreven, om een melding te geven aan uw toepassing van optimistische gelijktijdigheidsschendingen. RowUpdated vindt plaats na elke poging om een gewijzigde rij bij te werken vanuit een DataSet. Hiermee kunt u speciale afhandelingscode toevoegen, inclusief verwerking wanneer er een uitzondering optreedt, aangepaste foutgegevens toevoegen, logica voor opnieuw proberen toevoegen, enzovoort. Het RowUpdatedEventArgs object retourneert een eigenschap RecordsAffected die het aantal rijen bevat dat wordt beïnvloed door een bepaalde updateopdracht voor een gewijzigde rij in een tabel. Door de updateopdracht in te stellen om te testen op optimistische gelijktijdigheid, retourneert de eigenschap RecordsAffected als gevolg hiervan een waarde van 0 wanneer er een optimistische gelijktijdigheidsfout is opgetreden, omdat er geen records zijn bijgewerkt. Als dit het geval is, wordt er een uitzondering gegenereerd. Met de rowUpdated-gebeurtenis kunt u deze gebeurtenis afhandelen en de uitzondering voorkomen door een geschikte RowUpdatedEventArgs.Status-waarde in te stellen, zoals UpdateStatus.SkipCurrentRow. Zie DataAdapter-gebeurtenissen verwerken voor meer informatie over de gebeurtenis RowUpdated.

Desgewenst kunt u DataAdapter.ContinueUpdateOnError instellen op true, voordat u Update aanroept en reageert op de foutgegevens die zijn opgeslagen in de eigenschap RowError van een bepaalde rij wanneer de update is voltooid. Zie Rijfoutinformatie voor meer informatie.

Voorbeeld van optimistische gelijktijdigheid

Hier volgt een eenvoudig voorbeeld waarin de UpdateCommand van een DataAdapter wordt ingesteld om te testen op optimistische gelijktijdigheid en vervolgens de rowUpdated-gebeurtenis wordt gebruikt om te testen op optimistische gelijktijdigheidsschendingen. Wanneer er een optimistische gelijktijdigheidsschending wordt aangetroffen, stelt de toepassing de RowError in van de rij waarvoor de update is uitgegeven om een optimistische gelijktijdigheidsfout weer te geven.

Houd er rekening mee dat de parameterwaarden die worden doorgegeven aan de WHERE-component van de opdracht UPDATE, zijn toegewezen aan de oorspronkelijke waarden van hun respectieve kolommen.

' Assumes connection is a valid SqlConnection.  
Dim adapter As SqlDataAdapter = 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 " &  
  "(CustomerID, CompanyName) VALUES(@CustomerID, @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.  
Dim parameter As SqlParameter = 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.  
AddHandler adapter.RowUpdated, New SqlRowUpdatedEventHandler( _  
  AddressOf OnRowUpdated)  
  
Dim dataSet As DataSet = New DataSet()  
adapter.Fill(dataSet, "Customers")  
  
' Modify the DataSet contents.  
adapter.Update(dataSet, "Customers")  
  
Dim dataRow As DataRow  
  
For Each dataRow In dataSet.Tables("Customers").Rows  
    If dataRow.HasErrors Then
       Console.WriteLine(dataRow (0) & vbCrLf & dataRow.RowError)  
    End If  
Next  
  
Private Shared Sub OnRowUpdated( _  
  sender As object, args As SqlRowUpdatedEventArgs)  
   If args.RecordsAffected = 0  
      args.Row.RowError = "Optimistic Concurrency Violation!"  
      args.Status = UpdateStatus.SkipCurrentRow  
   End If  
End Sub  
// 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;  
  }  
}  

Zie ook