Книга: Обработка баз данных на Visual Basic®.NET

Обнаружение конфликтов при параллельном доступе к данным

Обнаружение конфликтов при параллельном доступе к данным

Разработчики, имеющие опыт создания многопользовательских баз данных, знают о потенциальных проблемах при параллельном доступе к данным, возникающих при попытке одновременного обновления одних и тех же данных со стороны нескольких пользователей. Допустим, что пользователь А считывает какие-то данные и пользователь Б считывает те же данные, затем пользователь А изменяет эти данные, а пользователь Б также пытается их изменить. Как можно решить эту проблему? Существует два основных способа решения конфликтных ситуаций при параллельном доступе к данным.

Первый способ — пессимистическая блокировка (или пессимистическое управление параллельностью) — решает проблему перезаписи пользователем Б изменений, внесенных пользователем А. Для этого после считывания данных пользователем А для их редактирования на эти данные накладывается блокировка. Эта блокировка запрещает доступ к данным со стороны других пользователей до тех пор, пока пользователь А не завершит работу с заблокированными данными и не снимет блокировку.

Этот способ особенно полезен при высокой степени параллельности выполняемых операций с данными или при необходимости всегда просматривать наиболее свежие данные. Такая ситуация обычно возникает в системах оперативного учета материальных запасов или системах учета заказов, когда пользователь для принятия заказа должен убедиться в наличии товаров на складе.

Основными недостатками пессимистического управления параллельностью являются дополнительные накладные расходы на управление блокировками данных, необходимость постоянного подключения к базе данных и недостаточная масштабируемость. Плохая масштабируемость, особенно при работе в распределенной среде (например, в Internet), может привести к блокировке записей на несколько десятков секунд или даже минут.

Второй способ — оптимистическая блокировка (или оптимистическое управление параллельностью) — основан на очень кратковременной блокировке записей во время их фактического обновления. Этот способ решает проблему управления блокировками и масштабируемости, а также прекрасно подходит для редактирования наборов данных, отключенных от базы данных. Но что произойдет, если пользователь Б захочет обновить данные, уже обновленные пользователем А? Один из вариантов решения этой проблемы основан на признании только последнего обновления. Однако этот способ годится только для ограниченного круга приложений.

Для использования оптимистической блокировки нужно определить, изменялись ли данные после их последнего извлечения, что называется нарушением параллельного доступа (concurrency violation). Для этого применяются две основные стратегии.

Первая основана на создании временной метки или уникального номера версии для каждой записи, которые изменяются при обновлении записи. Исходное значение или номер версии включаются в состав предложения WHERE в команде обновления.

Вторая стратегия заключается в сохранении исходных значений полей. Эти значения становятся дополнительными условиями предложения WHERE в команде обновления.

В обоих способах, если исходная запись изменена, условие предложения WHERE не удовлетворяется, запись не будет найдена и не будет обновлена.

НА ЗАМЕТКУ

При использовании оптимистической блокировки в модели ADO 2.X в случае нарушения параллельного доступа появляется сообщение о невозможности найти запись, а не о нарушении параллельного доступа.

В модели ADO.NET поддерживается только оптимистическая блокировка и в текущей версии нет встроенной поддержки пессимистической блокировки. В Visual Studio .NET предусмотрено несколько возможностей для реализации оптимистической блокировки. Эта поддержка находится в полном согласии с расширенной поддержкой распределенных, отключенных и асинхронных приложений.

Команды SQL UPDATE и DELETE, сгенерированные объектом CommandBuilder и программой-мастером Data Adapter Configuration Wizard, содержат предложение WHERE для определения конфликтов параллельного доступа. Рассмотрим более внимательно код из главы 6, "ADO.NET: объект DataAdapter", созданный с помощью программы-мастера Data Adapter Configuration Wizard. Для этого нужно открыть раздел кода с заголовком Windows Form Designer generated code (Код, сгенерированный конструктором Windows Form) в коде формы frmUpdates. Но сначала рассмотрим команду SQL UPDATE, которая приводится в листинге 7.1.

Листинг 7.1. Команда SQL UPDATE, созданная программой-мастером Data Adapter Configuration Wizard

UPDATE tblEmployee
SET FirstName = @FirstName,
 LastName = @LastName,
 DepartmentID = @DepartmentID,
 Salary = @Salary
WHERE
 (ID = @Original_ID) AND
 (DepartmentID = @Original_DepartmentID OR
  @Original_DepartmentID IS NULL AND DepartmentID IS NULL)
 AND (FirstName = @Original_FirstName)
 AND (LastName = @Original_LastName)
 AND (Salary = @Original_Salary OR
  @Original_Salary IS NULL AND Salary IS NULL)
SELECT FirstName, LastName, DepartmentID, Salary, ID
FROM tblEmployee WHERE (ID = @ID)

Эта команда SQL выглядит как обычная команда UPDATE, которая задает новые значения для четырех обновляемых полей в качестве параметров объекта UpdateCommand. Предложение WHERE содержит первичный ключ (ID), а также исходные значения для каждого поля и проверяет их соответствие текущим значениям записи в базе данных. Более того, эта команда SQL проверяет наличие неопределенных значений NULL в базе данных и полях таблицы tblEmployee.

Команда SELECT (заданная при конфигурировании объекта DataAdapter) располагается вслед за командой UPDATE после точки с запятой. Точка с запятой всегда используется для разделения команд в пакете команд SQL, а команда SELECT добавляется по умолчанию для возвращения обновленной записи в приложение.

Рассмотрим код указания параметров для объекта UpdateCommand, как показано в листинге 7.2.

Листинг 7.2. Код установки параметров команды, сгенерированной с помощью программы-мастера Data Adapter Configuration Wizard

Me.SqlUpdateCommand1.Parameters.Add(New System.Data.SqlClient.SqlParameter("@FirstName", System.Data.SqlDbType.VarChar, 50, "FirstName"))
Me.SqlUpdateCommand1.Parameters.Add(New System.Data.SqlClient.SqlParameter("@LastName", System.Data.SqlDbType.VarChar, 70, "LastName"))
Me.SqlUpdateCommand1.Parameters.Add(New System.Data.SqlClient.SqlParameter("@DepartmentID", System.Data.SqlDbType.Int, 4, "DepartmentID"))
Me.SqlUpdateCommand1.Parameters.Add(New System.Data.SqlClient.SqlParameter("@Salary", System.Data.SqlDbType.Money, 8, "Salary"))
Me.SqlUpdateCommand1.Parameters.Add(New System.Data.SqlClient.SqlParameter("@Original_ID", System.Data.SqlDbType.Int, 4, System.Data.ParameterDirection.Input, False, CType(0, Byte), CType(0, Byte), "ID", System.Data.DataRowVersion.Original, Nothing))
Me.SqlUpdateCommand1.Parameters.Add(New System.Data.SqlClient.SqlParameter("@Original_DepartmentID", System.Data.SqlDbType.Int, 4, System.Data.ParameterDirection.Input, False, CType(0, Byte), CType(0, Byte), "DepartmentID", System.Data.DataRowVersion.Original, Nothing))
Me.SqlUpdateCommand1.Parameters.Add(New System.Data.SqlClient.SqlParameter("@Original_FirstName", System.Data.SqlDbType.VarChar, 50, System.Data.ParameterDirection.Input, False, Byte), CType(0, Byte), "FirstName", System.Data.DataRowVersion.Original, Nothing))
Me.SqlUpdateCommand1.Parameters.Add(New System.Data.SqlClient.SqlParameter("@Original_LastName", System.Data.SqlDbType.VarChar, 70, System.Data.ParameterDirection.Input, False, CType(0, Byte), CType(0, Byte), "LastName", System.Data.DataRowVersion.Original, Nothing))
Me.SqlUpdateCommand1.Parameters.Add(New System.Data.SqlClient.SqlParameter(System.Data.SqlDbType.Money, 8, System.Data.ParameterDirection.Input, False, CType(0, Byte), CType(0, Byte), "Salary", System.Data.DataRowVersion.Original, Nothing))
Me.SqlUpdateCommand1.Parameters.Add(New System.Data.SqlClient.SqlParameter("@ID", System.Data.SqlDbType.Int, 4, "ID"))

Для данного объекта-команды заданы десять параметров. Первые четыре параметра являются текущими (возможно, измененными) значениями полей, которые следует обновить в базе данных. Как сообщается в главе 5, "ADO.NET: объект DataSet", каждая запись может иметь одну из четырех версий значения в каждой записи. По умолчанию используется текущее значение из считываемого поля.

Следующие пять параметров являются исходными значениями полей, которые упоминаются в предложении WHERE. Учтите, что для извлечения исходной версии значения поля (а не предлагаемой по умолчанию текущей версии) нужно явно указать исходную версию, как показано ниже.

System.Data.DataRowVersion.Original

НА ЗАМЕТКУ

Необязательно использовать исходные версии значения для всех полей в предложении WHERE. Можно настроить нужным образом любые команды обновления, чтобы уведомить об обновлении только одного или двух полей записи. В таком случае достаточно обновить только отдельные поля, а не все поля сразу. 

Последний параметр — текущее значение поля ID — применяется в качестве параметра команды SELECT, используемой для возвращения обновленных значений записи.

После каждой операции вставки, обновления или удаления объект DataAdapter проверяет количество строк, охваченных операцией. Если количество строк равно нулю, то генерируется исключительная ситуация DBConcurrencyException, поскольку обычно предполагается, что это результат конфликта при параллельном доступе к данным. Для ее перехвата и обработки следует включить блок Try-Catch в подпрограмму btnUpdate_Click, которая показана в листинге 7.3.

Листинг 7.3. Блок Try-Catch для обработки исключительной ситуации DBConcurrencyException

Private Sub btnUpdate_Click(ByVal sender As System.Object, _
 ByVal e As System.EventArgs) Handles btnUpdate.Click
 Try
  daEmployees.Update(dsEmployeeInfo, "Employees")
 Catch ec As DBConcurrencyException
  ' Выполнить какие-то действия.
 Catch es As DBConcurrencyException
  MessageBox.Show(es.Message)
 End Try
End Sub

Оглавление книги


Генерация: 1.661. Запросов К БД/Cache: 3 / 1
поделиться
Вверх Вниз