Dela via


Optimistisk samtidighet

I en miljö med flera användare finns det två modeller för att uppdatera data i en databas: optimistisk samtidighet och pessimistisk samtidighet. Objektet DataSet är utformat för att uppmuntra till optimistisk samtidighet för långvariga aktiviteter, till exempel fjärrkommunikation av data och interaktion med data.

Pessimistisk samtidighet innebär att du låser rader i datakällan för att förhindra att andra användare ändrar data på ett sätt som påverkar den aktuella användaren. I en pessimistisk modell, när en användare utför en åtgärd som gör att ett lås tillämpas, kan andra användare inte utföra åtgärder som skulle stå i konflikt med låset förrän låsägaren släpper det. Den här modellen används främst i miljöer där det råder stor konkurrens om data, så att kostnaden för att skydda data med lås är lägre än kostnaden för att återställa transaktioner om samtidighetskonflikter uppstår.

I en pessimistisk samtidighetsmodell upprättar därför en användare som uppdaterar en rad ett lås. Tills användaren har slutfört uppdateringen och släppt låset kan ingen annan ändra den raden. Därför implementeras pessimistisk samtidighet bäst när låstiderna blir korta, som vid programmatisk bearbetning av poster. Pessimistisk samtidighet är inte ett skalbart alternativ när användare interagerar med data och gör att poster låses under relativt stora tidsperioder.

Kommentar

Om du behöver uppdatera flera rader i samma åtgärd är det ett mer skalbart alternativ att skapa en transaktion än att använda pessimistisk låsning.

Användare som använder optimistisk samtidighet låser däremot inte en rad när de läser den. När en användare vill uppdatera en rad måste programmet avgöra om en annan användare har ändrat raden sedan den lästes. Optimistisk samtidighet används vanligtvis i miljöer med låg konkurrens om data. Optimistisk samtidighet förbättrar prestanda eftersom ingen låsning av poster krävs, och låsning av poster kräver ytterligare serverresurser. För att upprätthålla postlås krävs också en beständig anslutning till databasservern. Eftersom detta inte är fallet i en optimistisk samtidighetsmodell är anslutningar till servern fria att betjäna ett större antal klienter på kortare tid.

I en optimistisk samtidighetsmodell anses en överträdelse ha inträffat om en annan användare, efter att ha fått ett värde från databasen, ändrar värdet innan den första användaren har försökt ändra det. Hur servern löser en samtidighetsöverträdelse visas bäst genom att först beskriva följande exempel.

Följande tabeller följer ett exempel på optimistisk samtidighet.

Klockan 13:00 läser User1 en rad från databasen med följande värden:

CustID LastName FirstName

101 Smed bob

Kolumnnamn Ursprungligt värde Aktuellt värde Värde i databasen
CustID 101 101 101
LastName Smith Smith Smith
FirstName Johan Johan Johan

Klockan 13:01 läser User2 samma rad.

Klockan 13:03 ändrar User2 FirstName från "Bob" till "Robert" och uppdaterar databasen.

Kolumnnamn Ursprungligt värde Aktuellt värde Värde i databasen
CustID 101 101 101
LastName Smith Smith Smith
FirstName Johan Robert Johan

Uppdateringen lyckas eftersom värdena i databasen vid tidpunkten för uppdateringen matchar de ursprungliga värden som User2 har.

Klockan 13:05 ändrar User1 "Bobs förnamn till "James" och försöker uppdatera raden.

Kolumnnamn Ursprungligt värde Aktuellt värde Värde i databasen
CustID 101 101 101
LastName Smith Smith Smith
FirstName Johan James Robert

Nu stöter User1 på en optimistisk samtidighetsöverträdelse eftersom värdet i databasen ("Robert") inte längre matchar det ursprungliga värdet som User1 förväntade sig ("Bob"). Samtidighetsöverträdelsen låter dig bara veta att uppdateringen misslyckades. Beslutet måste nu fattas om du vill skriva över de ändringar som tillhandahålls av User2 med de ändringar som tillhandahålls av User1 eller avbryta ändringarna av User1.

Testning för optimistiska samtidighetsöverträdelser

Det finns flera tekniker för testning för en optimistisk samtidighetsöverträdelse. Det ena handlar om att inkludera en tidsstämpelkolumn i tabellen. Databaser tillhandahåller ofta tidsstämpelfunktioner som kan användas för att identifiera datum och tid när posten senast uppdaterades. Med den här tekniken ingår en tidsstämpelkolumn i tabelldefinitionen. När posten uppdateras uppdateras tidsstämpeln så att den återspeglar aktuellt datum och tid. I ett test för optimistiska samtidighetsöverträdelser returneras tidsstämpelkolumnen med alla frågor i tabellens innehåll. När en uppdatering görs jämförs tidsstämpelvärdet i databasen med det ursprungliga tidsstämpelvärdet i den ändrade raden. Om de matchar utförs uppdateringen och tidsstämpelkolumnen uppdateras med den aktuella tiden för att återspegla uppdateringen. Om de inte matchar har en optimistisk samtidighetsöverträdelse inträffat.

En annan metod för att testa en optimistisk samtidighetsöverträdelse är att kontrollera att alla ursprungliga kolumnvärden i en rad fortfarande matchar de som finns i databasen. Tänk dig följande fråga:

SELECT Col1, Col2, Col3 FROM Table1  

Om du vill testa en optimistisk samtidighetsöverträdelse när du uppdaterar en rad i Table1 utfärdar du följande UPDATE-instruktion:

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

Så länge de ursprungliga värdena matchar värdena i databasen utförs uppdateringen. Om ett värde har ändrats ändrar uppdateringen inte raden eftersom WHERE-satsen inte hittar någon matchning.

Observera att du alltid bör returnera ett unikt primärnyckelvärde i frågan. Annars kan föregående UPDATE-instruktion uppdatera mer än en rad, vilket kanske inte är din avsikt.

Om en kolumn i datakällan tillåter null-värden kan du behöva utöka WHERE-satsen för att söka efter en matchande null-referens i den lokala tabellen och vid datakällan. Följande UPDATE-instruktion verifierar till exempel att en nullreferens på den lokala raden fortfarande matchar en null-referens i datakällan, eller att värdet på den lokala raden fortfarande matchar värdet i datakällan.

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

Du kan också välja att tillämpa mindre restriktiva kriterier när du använder en optimistisk samtidighetsmodell. Om du till exempel bara använder primärnyckelkolumnerna i WHERE-satsen skrivs data över oavsett om de andra kolumnerna har uppdaterats sedan den senaste frågan. Du kan också använda en WHERE-sats endast för specifika kolumner, vilket resulterar i att data skrivs över om inte vissa fält har uppdaterats sedan de senast efterfrågades.

Händelsen DataAdapter.RowUpdated

RowUpdated-händelsen för DataAdapter objektet kan användas tillsammans med de tekniker som beskrevs tidigare för att meddela ditt program om optimistiska samtidighetsöverträdelser. RowUpdated inträffar efter varje försök att uppdatera en ändrad rad från en DataSet. På så sätt kan du lägga till särskild hanteringskod, inklusive bearbetning när ett undantag inträffar, lägga till anpassad felinformation, lägga till logik för återförsök och så vidare. Objektet RowUpdatedEventArgs returnerar en RecordsAffected-egenskap som innehåller antalet rader som påverkas av ett visst uppdateringskommando för en ändrad rad i en tabell. Genom att ställa in uppdateringskommandot för att testa optimistisk samtidighet returnerar egenskapen RecordsAffected därför värdet 0 när en optimistisk samtidighetsöverträdelse har inträffat eftersom inga poster uppdaterades. I så fall utlöses ett undantag. Med händelsen RowUpdated kan du hantera den här förekomsten och undvika undantaget genom att ange ett lämpligt RowUpdatedEventArgs.Status-värde , till exempel UpdateStatus.SkipCurrentRow. Mer information om händelsen RowUpdated finns i Hantera DataAdapter-händelser.

Du kan också ange DataAdapter.ContinueUpdateOnError till true innan du anropar Uppdatera och svara på felinformationen som lagras i egenskapen RowError för en viss rad när uppdateringen är klar. Mer information finns i Radfelinformation.

Optimistiskt samtidighetsexempel

Följande är ett enkelt exempel som anger UpdateCommand för en DataAdapter för att testa optimistisk samtidighet och sedan använder händelsen RowUpdated för att testa optimistiska samtidighetsöverträdelser. När en optimistisk samtidighetsöverträdelse påträffas anger programmet RowError för raden som uppdateringen utfärdades för för att återspegla en optimistisk samtidighetsöverträdelse.

Observera att parametervärdena som skickas till WHERE-satsen i UPDATE-kommandot mappas till de ursprungliga värdena för respektive kolumner.

' 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;  
  }  
}  

Se även