Accès concurrentiel optimisteOptimistic Concurrency

Dans un environnement multi-utilisateur, il existe deux modèles pour la mise à jour de données dans une base de données : l'accès simultané optimiste et l'accès simultané pessimiste.In a multiuser environment, there are two models for updating data in a database: optimistic concurrency and pessimistic concurrency. L'objet DataSet est conçu pour privilégier l'utilisation de l'accès simultané optimiste pour les activités longues, comme lors de la communication à distance de données ou de l'interaction avec ces dernières.The DataSet object is designed to encourage the use of optimistic concurrency for long-running activities, such as remoting data and interacting with data.

L'accès simultané pessimiste implique le verrouillage de lignes à la source de données pour empêcher que d'autres utilisateurs modifient des données d'une manière affectant l'utilisateur actuel.Pessimistic concurrency involves locking rows at the data source to prevent other users from modifying data in a way that affects the current user. Dans un modèle pessimiste, lorsqu'un utilisateur effectue une action entraînant l'application d'un verrou, les autres utilisateurs ne peuvent pas effectuer d'actions qui créeraient un conflit avec le verrou tant que le propriétaire de ce dernier ne l'a pas libéré.In a pessimistic model, when a user performs an action that causes a lock to be applied, other users cannot perform actions that would conflict with the lock until the lock owner releases it. Ce modèle est principalement utilisé dans les environnements où les conflits relatifs aux données sont fréquents, de sorte que le coût de la protection des données par verrous est inférieur à celui de la restauration des transactions en cas de conflits d'accès simultané.This model is primarily used in environments where there is heavy contention for data, so that the cost of protecting data with locks is less than the cost of rolling back transactions if concurrency conflicts occur.

Par conséquent, dans un modèle d'accès simultané pessimiste, un utilisateur qui met à jour une ligne crée un verrou.Therefore, in a pessimistic concurrency model, a user who updates a row establishes a lock. Jusqu'à ce que cet utilisateur ait terminé sa mise à jour et libéré le verrou, personne d'autre ne peut modifier cette ligne.Until the user has finished the update and released the lock, no one else can change that row. C'est pourquoi il est préférable d'implémenter l'accès simultané pessimiste lorsque les temps de verrouillage sont courts, comme c'est le cas pour le traitement d'enregistrements par programme.For this reason, pessimistic concurrency is best implemented when lock times will be short, as in programmatic processing of records. L'accès simultané pessimiste ne constitue pas la solution la plus adaptée lorsque des utilisateurs interagissent avec les données, ce qui entraîne le verrouillage d'enregistrements pendant des laps de temps relativement longs.Pessimistic concurrency is not a scalable option when users are interacting with data and causing records to be locked for relatively large periods of time.

Note

Si vous devez mettre à jour plusieurs lignes en une même opération, la création d'une transaction constitue une option plus adaptée que l'utilisation du verrouillage pessimiste.If you need to update multiple rows in the same operation, then creating a transaction is a more scalable option than using pessimistic locking.

Au contraire, les utilisateurs qui ont recours à un accès simultané optimiste ne verrouillent pas une ligne lorsqu'ils la lisent.By contrast, users who use optimistic concurrency do not lock a row when reading it. Lorsqu'un utilisateur souhaite mettre à jour une ligne, l'application doit déterminer si un autre utilisateur a modifié cette ligne depuis sa dernière lecture.When a user wants to update a row, the application must determine whether another user has changed the row since it was read. L'accès simultané optimiste est généralement utilisé dans les environnements où les conflits relatifs aux données sont rares.Optimistic concurrency is generally used in environments with a low contention for data. L'accès simultané optimiste améliore les performances, dans la mesure où aucun verrouillage des enregistrements n'est requis et où le verrouillage d'enregistrements nécessite des ressources serveur supplémentaires.Optimistic concurrency improves performance because no locking of records is required, and locking of records requires additional server resources. Il faut également savoir que la gestion des verrous d'enregistrements requiert une connexion permanente au serveur de base de données.Also, in order to maintain record locks, a persistent connection to the database server is required. Parce que ce n'est pas le cas dans un modèle d'accès simultané optimiste, les connexions au serveur sont disponibles pour traiter plus rapidement un nombre important de clients.Because this is not the case in an optimistic concurrency model, connections to the server are free to serve a larger number of clients in less time.

Dans un modèle d'accès simultané optimiste, une violation est réputée avoir eu lieu si, après réception par un utilisateur d'une valeur provenant de la base de données, un autre utilisateur modifie cette valeur avant que le premier n'ait tenté de le faire.In an optimistic concurrency model, a violation is considered to have occurred if, after a user receives a value from the database, another user modifies the value before the first user has attempted to modify it. La manière dont le serveur résout une violation de l'accès simultané est bien illustrée par l'exemple suivant.How the server resolves a concurrency violation is best shown by first describing the following example.

Les tableaux ci-après suivent un exemple d'accès simultané optimiste.The following tables follow an example of optimistic concurrency.

À 13h00, l'utilisateur 1 lit une ligne de la base de données contenant les valeurs suivantes :At 1:00 p.m., User1 reads a row from the database with the following values:

CustID LastName FirstNameCustID LastName FirstName

101 Bob Smith101 Smith Bob

Nom de la colonneColumn name Valeur d'origineOriginal value Valeur actuelleCurrent value Valeur dans la base de donnéesValue in database
CustIDCustID 101101 101101 101101
LastNameLastName SmithSmith SmithSmith SmithSmith
FirstNameFirstName BobBob BobBob BobBob

À 13h01, l'utilisateur 2 lit la même ligne.At 1:01 p.m., User2 reads the same row.

À 13 h 03, l’utilisateur 2 modifie FirstName de « Bob » par « Robert » et met à jour de la base de données.At 1:03 p.m., User2 changes FirstName from "Bob" to "Robert" and updates the database.

Nom de la colonneColumn name Valeur d'origineOriginal value Valeur actuelleCurrent value Valeur dans la base de donnéesValue in database
CustIDCustID 101101 101101 101101
LastNameLastName SmithSmith SmithSmith SmithSmith
FirstNameFirstName BobBob RobertRobert BobBob

La mise à jour réussit car les valeurs de la base de données au moment de la mise à jour correspondent aux valeurs d'origine dont dispose l'utilisateur 2.The update succeeds because the values in the database at the time of update match the original values that User2 has.

À 13h05, l'utilisateur 1 modifie la valeur de prénom pour remplacer « Bob » par « James » et tente de mettre à jour la base de données.At 1:05 p.m., User1 changes "Bob"'s first name to "James" and tries to update the row.

Nom de la colonneColumn name Valeur d'origineOriginal value Valeur actuelleCurrent value Valeur dans la base de donnéesValue in database
CustIDCustID 101101 101101 101101
LastNameLastName SmithSmith SmithSmith SmithSmith
FirstNameFirstName BobBob JamesJames RobertRobert

À ce stade, l'utilisateur 1 est confronté à une violation d'accès simultané optimiste, car la valeur de la base de données (« Robert ») ne correspond plus à la valeur d'origine qu'attendait l'utilisateur 1 (« Bob »).At this point, User1 encounters an optimistic concurrency violation because the value in the database ("Robert") no longer matches the original value that User1 was expecting ("Bob"). La violation d'accès concurrentiel vous indique simplement que la mise à jour a échoué.The concurrency violation simply lets you know that the update failed. Il convient maintenant de décider si les modifications apportées par l'utilisateur 2 devront être remplacées par celles de l'utilisateur 1 ou si ces dernières devront être annulées.The decision now needs to be made whether to overwrite the changes supplied by User2 with the changes supplied by User1, or to cancel the changes by User1.

Recherche des violations d'accès simultané optimisteTesting for Optimistic Concurrency Violations

Il existe plusieurs techniques qui permettent de déceler la présence d'une violation d'accès simultané optimiste.There are several techniques for testing for an optimistic concurrency violation. L'une d'entre elles consiste à inclure une colonne horodateur dans la table.One involves including a timestamp column in the table. Les bases de données proposent généralement une fonctionnalité d'horodatage qui peut être utilisée pour connaître la date et l'heure de la dernière mise à jour d'un enregistrement.Databases commonly provide timestamp functionality that can be used to identify the date and time when the record was last updated. En utilisant cette technique, une colonne horodateur est incluse dans la définition de la table.Using this technique, a timestamp column is included in the table definition. Chaque fois que l'enregistrement est mis à jour, l'horodatage est également mis à jour en fonction de la date et de l'heure actuelles.Whenever the record is updated, the timestamp is updated to reflect the current date and time. Dans un test visant à déceler la présence de violations d'accès simultané optimiste, la colonne horodateur est retournée avec toute requête liée au contenu de la table.In a test for optimistic concurrency violations, the timestamp column is returned with any query of the contents of the table. Lors d'une tentative de mise à jour, la valeur d'horodatage figurant dans la base de données est comparée à la valeur d'origine contenue dans la ligne modifiée.When an update is attempted, the timestamp value in the database is compared to the original timestamp value contained in the modified row. Si les valeurs correspondent, la modification est apportée et la colonne horodateur est mise à jour en fonction de la date et de l'heure actuelles afin de refléter la modification.If they match, the update is performed and the timestamp column is updated with the current time to reflect the update. Si elles ne correspondent pas, une violation d'accès simultané optimiste s'est produite.If they do not match, an optimistic concurrency violation has occurred.

Une autre technique de recherche des violations d'accès simultané optimiste consiste à vérifier que toutes les valeurs de colonne d'origine d'une ligne correspondent toujours à celles qui figurent dans la base de données.Another technique for testing for an optimistic concurrency violation is to verify that all the original column values in a row still match those found in the database. Examinons, par exemple, la requête suivante :For example, consider the following query:

SELECT Col1, Col2, Col3 FROM Table1  

Pour tester une violation d’accès concurrentiel optimiste lors de la mise à jour une ligne dans Table1, émettez l’instruction UPDATE suivante :To test for an optimistic concurrency violation when updating a row in Table1, you would issue the following UPDATE statement:

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

Tant que les valeurs d'origine correspondent à celles qui figurent dans la base de données, la mise à jour est effectuée.As long as the original values match the values in the database, the update is performed. Si une valeur a été modifiée, la mise à jour ne modifiera pas la ligne car la clause WHERE ne trouvera pas de correspondance.If a value has been modified, the update will not modify the row because the WHERE clause will not find a match.

Notez qu'il est recommandé de toujours retourner une valeur de clé primaire unique dans votre requête.Note that it is recommended to always return a unique primary key value in your query. Sinon, l'instruction UPDATE précédente risque de mettre à jour plusieurs lignes, ce qui n'est pas nécessairement dans vos intentions.Otherwise, the preceding UPDATE statement may update more than one row, which might not be your intent.

Si une colonne de votre source de données accepte les valeurs null, vous devrez peut-être étendre votre clause WHERE pour vérifier s'il existe une référence null dans votre table locale et sa correspondance dans votre source de données.If a column at your data source allows nulls, you may need to extend your WHERE clause to check for a matching null reference in your local table and at the data source. Par exemple, l'instruction UPDATE suivante vérifie qu'une référence null dans la ligne locale correspond toujours à une référence null dans la source de données, ou si la valeur dans la ligne locale correspond toujours à celle de la source de données.For example, the following UPDATE statement verifies that a null reference in the local row still matches a null reference at the data source, or that the value in the local row still matches the value at the data source.

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

Vous pouvez aussi choisir d'appliquer des critères moins restrictifs lorsque vous utilisez un modèle d'accès simultané optimiste.You may also choose to apply less restrictive criteria when using an optimistic concurrency model. Par exemple, utiliser uniquement les colonnes de clé primaire dans la clause WHERE aboutit au remplacement des données, que les autres colonnes aient ou non subi une mise à jour depuis la dernière requête.For example, using only the primary key columns in the WHERE clause causes the data to be overwritten regardless of whether the other columns have been updated since the last query. Vous pouvez aussi appliquer une clause WHERE à certaines colonnes uniquement, ce qui aura pour effet de remplacer les données, sauf si des champs spécifiques ont été mis à jour depuis la dernière requête les concernant.You can also apply a WHERE clause only to specific columns, resulting in data being overwritten unless particular fields have been updated since they were last queried.

Événement DataAdapter.RowUpdatedThe DataAdapter.RowUpdated Event

Le RowUpdated événements de la DataAdapter objet peut être utilisé conjointement avec les techniques précédemment décrites, afin de fournir une notification à votre application de violations d’accès concurrentiel optimiste.The RowUpdated event of the DataAdapter object can be used in conjunction with the techniques described earlier, to provide notification to your application of optimistic concurrency violations. RowUpdated intervient après chaque tentative pour mettre à jour un Modified ligne à partir d’un DataSet.RowUpdated occurs after each attempt to update a Modified row from a DataSet. Cela vous permet d'ajouter un code de gestion spécial, qui traitera les exceptions le cas échéant, ajoutera des informations d'erreur personnalisées, ajoutera une logique pour les nouvelles tentatives, etc.This enables you to add special handling code, including processing when an exception occurs, adding custom error information, adding retry logic, and so on. Le RowUpdatedEventArgs objet retourne un RecordsAffected propriété contenant le nombre de lignes affectées par une commande de mise à jour particulière pour une ligne modifiée dans une table.The RowUpdatedEventArgs object returns a RecordsAffected property containing the number of rows affected by a particular update command for a modified row in a table. En définissant la commande de mise à jour pour tester l’accès concurrentiel optimiste, la RecordsAffected propriété, par conséquent, renvoie la valeur 0 lorsqu’une violation d’accès concurrentiel optimiste s’est produite, car aucun enregistrement ont été mis à jour.By setting the update command to test for optimistic concurrency, the RecordsAffected property will, as a result, return a value of 0 when an optimistic concurrency violation has occurred, because no records were updated. Dans ce cas, une exception est levée.If this is the case, an exception is thrown. Le RowUpdated événement vous permet de gérer cet événement et d’éviter l’exception en définissant une RowUpdatedEventArgs.Status valeur, telle que UpdateStatus.SkipCurrentRow.The RowUpdated event enables you to handle this occurrence and avoid the exception by setting an appropriate RowUpdatedEventArgs.Status value, such as UpdateStatus.SkipCurrentRow. Pour plus d’informations sur la RowUpdated événement, consultez gestion des événements DataAdapter.For more information about the RowUpdated event, see Handling DataAdapter Events.

Si vous le souhaitez, vous pouvez définir DataAdapter.ContinueUpdateOnError à true, avant d’appeler mise à jouret répondre aux informations d’erreur stockées dans le RowError ligne de propriété d’un particulier lorsque le mise à jour est terminée.Optionally, you can set DataAdapter.ContinueUpdateOnError to true, before calling Update, and respond to the error information stored in the RowError property of a particular row when the Update is completed. Pour plus d’informations, consultez informations d’erreur de ligne.For more information, see Row Error Information.

Exemple d'accès simultané optimisteOptimistic Concurrency Example

Voici un exemple simple qui définit les UpdateCommand d’un DataAdapter pour tester l’accès concurrentiel optimiste et utilise ensuite le RowUpdated événement à tester pour violations d’accès concurrentiel optimiste.The following is a simple example that sets the UpdateCommand of a DataAdapter to test for optimistic concurrency, and then uses the RowUpdated event to test for optimistic concurrency violations. Une violation d’accès concurrentiel optimiste, l’application définit le RowError de la ligne de la mise à jour a été émis pour afin de refléter une violation d’accès concurrentiel optimiste.When an optimistic concurrency violation is encountered, the application sets the RowError of the row that the update was issued for to reflect an optimistic concurrency violation.

Notez que les valeurs de paramètre passées à la clause WHERE de la commande de mise à jour sont mappées à la d’origine valeurs de leurs colonnes respectives.Note that the parameter values passed to the WHERE clause of the UPDATE command are mapped to the Original values of their respective columns.

' 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 = dataSet.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;  
  }  
}  

Voir aussiSee Also

Extraction et modification de données dans ADO.NETRetrieving and Modifying Data in ADO.NET
Mise à jour de sources de données avec des DataAdaptersUpdating Data Sources with DataAdapters
Informations sur l’erreur de ligneRow Error Information
Transactions et accès concurrentielTransactions and Concurrency
Fournisseurs managés ADO.NET et centre de développement DataSetADO.NET Managed Providers and DataSet Developer Center