Оптимистическая блокировкаOptimistic Concurrency

В многопользовательской среде предусмотрены две модели обновления данных в базе данных: оптимистичный параллелизм и пессимистичный параллелизм.In a multiuser environment, there are two models for updating data in a database: optimistic concurrency and pessimistic concurrency. Объект DataSet предназначен для стимулирования использования оптимистичного параллелизма для длительный действий, таких как удаленный доступ к данным и взаимодействие с данными.The DataSet object is designed to encourage the use of optimistic concurrency for long-running activities, such as remoting data and interacting with data.

Пессимистичный параллелизм предусматривает блокировку строк в источнике данных, что позволяет запретить другим пользователям модификацию данных, которая может отрицательно повлиять на работу текущего пользователя.Pessimistic concurrency involves locking rows at the data source to prevent other users from modifying data in a way that affects the current user. В пессимистической модели, когда пользователь выполняет действие, вызывающее применение блокировки, другие пользователи не могут выполнять действия, которые могли бы конфликтовать с этой блокировкой, до тех пор как владелец блокировки ее не снимет.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. Эта модель в основном используется в таких средах, где наблюдается интенсивная конкуренция по доступу к данным, поэтому расходы на защиту данных с помощью блокировок оказываются меньше по сравнению с расходами на откаты транзакций, обусловленные возникновением конфликтов параллельного доступа.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.

Поэтому в модели с пессимистичным параллелизмом пользователь, обновляющий строку, устанавливает блокировку.Therefore, in a pessimistic concurrency model, a user who updates a row establishes a lock. До тех пор пока пользователь не закончит обновление и не снимет блокировку, больше никто другой не сможет вносить изменения в эту строку.Until the user has finished the update and released the lock, no one else can change that row. Это означает, что пессимистичный параллелизм является наиболее подходящим в тех ситуациях, когда блокировки устанавливаются на короткое время, например как при программной обработке записей.For this reason, pessimistic concurrency is best implemented when lock times will be short, as in programmatic processing of records. Применение пессимистичного параллелизма становится препятствием для масштабирования приложения, когда пользователи взаимодействуют с данными и вызывают блокировку записей на относительно большие промежутки времени.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.

Примечание

Если в приложении требуется обновлять несколько строк в одной операции, то создание транзакций обеспечивает лучшее масштабирование по сравнению с использованием пессимистичных блокировок.If you need to update multiple rows in the same operation, then creating a transaction is a more scalable option than using pessimistic locking.

В отличие от этого, пользователи, применяющие оптимистичный параллелизм, не блокируют строку при ее чтении.By contrast, users who use optimistic concurrency do not lock a row when reading it. Когда пользователю требуется обновить строку, приложение должно определить, не изменялась ли эта строка другим пользователем после того, как она была открыта для чтения.When a user wants to update a row, the application must determine whether another user has changed the row since it was read. Оптимистичный параллелизм обычно используется в средах, характеризующихся низкой конкуренцией за данные.Optimistic concurrency is generally used in environments with a low contention for data. Применение оптимистичного параллелизма способствует повышению производительности благодаря тому, что не требуется блокировка записей, которая требует дополнительных ресурсов сервера.Optimistic concurrency improves performance because no locking of records is required, and locking of records requires additional server resources. Кроме того, для блокировки записей требуется постоянное соединение с сервером базы данных.Also, in order to maintain record locks, a persistent connection to the database server is required. При использовании модели оптимистичного параллелизма такая необходимость отсутствует, поэтому соединения с сервером можно применять для обслуживания большего количества клиентов за меньшее время.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.

В модели оптимистичного параллелизма конфликтом параллельного доступа считается ситуация, когда вслед за получением пользователем значения из базы данных другой пользователь изменяет это значение до того, как первый пользователь попытается его изменить.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. Разрешение конфликта параллельного доступа лучше всего показать, рассмотрев следующий пример.How the server resolves a concurrency violation is best shown by first describing the following example.

В следующих таблицах рассматривается пример оптимистичного параллелизма.The following tables follow an example of optimistic concurrency.

В 13:00 Пользователь1 считывает из базы данных строку со следующими значениями:At 1:00 p.m., User1 reads a row from the database with the following values:

CustID LastName FirstNameCustID LastName FirstName

101 Smith Bob101 Smith Bob

Имя столбцаColumn name Исходное значениеOriginal value Текущее значениеCurrent value Значение в базе данныхValue in database
CustIDCustID 101101 101101 101101
LastNameLastName SmithSmith SmithSmith SmithSmith
FirstNameFirstName BobBob BobBob BobBob

В 13:01 Пользователь2 считывает ту же строку.At 1:01 p.m., User2 reads the same row.

В 1:03 г. Пользователь2 изменяет имя с "Bob" на "Роберт" и обновляет базу данных.At 1:03 p.m., User2 changes FirstName from "Bob" to "Robert" and updates the database.

Имя столбцаColumn name Исходное значениеOriginal value Текущее значениеCurrent value Значение в базе данныхValue in database
CustIDCustID 101101 101101 101101
LastNameLastName SmithSmith SmithSmith SmithSmith
FirstNameFirstName BobBob RobertRobert BobBob

Эта операция обновления завершается успешно, поскольку значения в базе данных ко времени обновления соответствуют первоначальным значениям, которые имеются в распоряжении Пользователя2.The update succeeds because the values in the database at the time of update match the original values that User2 has.

В 13:05 Пользователь1 изменяет значение FirstName с «Bob» на «James» и пытается обновить строку.At 1:05 p.m., User1 changes "Bob"'s first name to "James" and tries to update the row.

Имя столбцаColumn name Исходное значениеOriginal value Текущее значениеCurrent value Значение в базе данныхValue in database
CustIDCustID 101101 101101 101101
LastNameLastName SmithSmith SmithSmith SmithSmith
FirstNameFirstName BobBob JamesJames RobertRobert

В этот момент Пользователь1 обнаруживает нарушение оптимистичного параллелизма, поскольку значение в базе данных («Robert») больше не соответствует первоначальному значению, которое ожидал найти Пользователь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"). Это нарушение параллелизма просто позволяет узнать о том, что обновление окончилось неудачей.The concurrency violation simply lets you know that the update failed. После этого нужно принять решение, следует ли перезаписать изменения, внесенные Пользователем2, изменениями, которые внес Пользователь1, или отменить изменения Пользователя1.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.

Проверка на наличие нарушений оптимистичного параллелизмаTesting for Optimistic Concurrency Violations

Существует несколько методов проверки на наличие нарушений оптимистичного параллелизма.There are several techniques for testing for an optimistic concurrency violation. Один из этих методов предусматривает включение столбца метки времени в таблицу.One involves including a timestamp column in the table. Базы данных обычно предоставляют возможность использования отметок времени, которые могут служить для определения даты и времени последнего обновления записи.Databases commonly provide timestamp functionality that can be used to identify the date and time when the record was last updated. При использовании этого метода в определение таблицы включается столбец метки времени.Using this technique, a timestamp column is included in the table definition. После каждого обновления записи обновляется также метка времени, отражая текущую дату и время.Whenever the record is updated, the timestamp is updated to reflect the current date and time. При проверке на наличие нарушений оптимистичного параллелизма при любом запросе к содержимому таблицы происходит возврат значений столбца метки времени.In a test for optimistic concurrency violations, the timestamp column is returned with any query of the contents of the table. При попытке обновления это значение метки времени из базы данных сравнивается с первоначальным значением метки времени, содержащимся в измененной строке.When an update is attempted, the timestamp value in the database is compared to the original timestamp value contained in the modified row. Если эти значения совпадают, операция обновления выполняется и столбец метки времени обновляется с указанием текущего времени, чтобы отразить факт обновления.If they match, the update is performed and the timestamp column is updated with the current time to reflect the update. Если эти значения не совпадают, то возникает конфликт оптимистичного параллелизма.If they do not match, an optimistic concurrency violation has occurred.

Еще один метод проверки на наличие нарушения оптимистичного параллелизма состоит в проверке того, что все первоначальные значения полей в строке все еще соответствуют находящимся в базе данных.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. Например, рассмотрим следующий запрос:For example, consider the following query:

SELECT Col1, Col2, Col3 FROM Table1  

Чтобы проверить наличие нарушения оптимистической блокировки при обновлении строки в Table1, выполните следующую инструкцию UPDATE: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  

При условии, что первоначальные значения равны значениям в базе данных, обновление выполняется.As long as the original values match the values in the database, the update is performed. Если же какое-то значение было изменено, то при обновлении изменение строки не происходит, поскольку в предложении WHERE не обнаруживается совпадение.If a value has been modified, the update will not modify the row because the WHERE clause will not find a match.

Обратите внимание, что всегда рекомендуется возвращать уникальное значение первичного ключа в каждом конкретном запросе.Note that it is recommended to always return a unique primary key value in your query. В противном случае в предыдущей инструкции UPDATE может произойти обновление нескольких строк, что может не соответствовать намерениям.Otherwise, the preceding UPDATE statement may update more than one row, which might not be your intent.

Если столбец в источнике данных допускает значения NULL, то может потребоваться дополнить предложение WHERE для проверки на наличие согласующихся ссылок NULL в локальной таблице и в источнике данных.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. Например, в следующей инструкции UPDATE проверяется, что ссылка NULL в локальной строке все еще соответствует ссылке NULL в источнике данных или что значение в локальной строке все еще соответствует значению в источнике данных.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  

При использовании модели оптимистичного параллелизма можно также выбрать менее строгие критерии.You may also choose to apply less restrictive criteria when using an optimistic concurrency model. Например, если в предложении WHERE используются только столбцы первичного ключа, то перезапись данных происходит независимо от того, произошло ли обновление других столбцов со времени выполнения последнего запроса.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. Предложение WHERE также можно применять только к конкретным столбцам, в результате чего перезапись данных будет выполняться, только если со времени последнего запроса к определенным полям они не обновлялись.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.

Событие DataAdapter.RowUpdatedThe DataAdapter.RowUpdated Event

Событие RowUpdated объекта DataAdapter можно использовать в сочетании с методами, описанными выше, чтобы обеспечить уведомление для приложения о нарушениях оптимистической блокировки.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 происходит после каждой попытки обновить измененную строку из набора данных.RowUpdated occurs after each attempt to update a Modified row from a DataSet. Это позволяет включать в приложение специальный код обработки, в том числе обеспечивающий обработку при возникновении исключений, добавление пользовательских сведений об ошибке, введение программных средств повторного выполнения и т. д.This enables you to add special handling code, including processing when an exception occurs, adding custom error information, adding retry logic, and so on. Объект RowUpdatedEventArgs возвращает свойство рекордсаффектед , содержащее количество строк, затронутых определенной командой Update для измененной строки в таблице.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. Настроив команду Update для проверки оптимистического параллелизма, свойство рекордсаффектед в результате возвращает значение 0 при возникновении нарушения оптимистического параллелизма, поскольку записи не обновлялись.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. В таком случае вызывается исключение.If this is the case, an exception is thrown. Событие RowUpdated позволяет справиться с этим экземпляром и избежать исключения, установив соответствующее значение ровупдатедевентаргс. status , например UpdateStatus. скипкуррентров.The RowUpdated event enables you to handle this occurrence and avoid the exception by setting an appropriate RowUpdatedEventArgs.Status value, such as UpdateStatus.SkipCurrentRow. Дополнительные сведения о событии RowUpdated см. в разделе Обработка событий DataAdapter.For more information about the RowUpdated event, see Handling DataAdapter Events.

При необходимости можно установить DataAdapter. континуеупдатеонеррор в значение true, перед вызовом Updateи ответ на сведения об ошибке, хранящиеся в свойстве роверрор определенной строки после завершения обновления .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. Дополнительные сведения см. в разделе сведения об ошибках строки.For more information, see Row Error Information.

Пример оптимистического управления параллелизмомOptimistic Concurrency Example

Ниже приведен простой пример, который задает UpdateCommand объекта DataAdapter для проверки на оптимистичный параллелизм, а затем использует событие RowUpdated для проверки на наличие нарушений оптимистической блокировки.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. При обнаружении нарушения оптимистичного параллелизма приложение устанавливает роверрор строки, для которой было выдано обновление, чтобы отразить нарушение оптимистического параллелизма.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.

Обратите внимание, что значения параметров, передаваемые в предложение WHERE команды UPDATE, сопоставляются исходным значениям соответствующих столбцов.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 = 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;  
  }  
}  

См. такжеSee also