Когда множество приложений и процессов пытаются одновременно модифицировать информацию в таблицах базы данных, необходимо использовать механизм контроля, способный разрешать конфликты и изолировать друг от друга конкурирующие запросы. Такой механизм в Microsoft SQL Server называется concurrency control - управление одновременным согласованным доступом. Существует две модели управления одновременным доступом пользователей к данным - пессимистическая и оптимистическая.

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

В оптимистической модели, напротив, пользователь при считывании записи не налагает на нее никаких блокировок. Когда пользователь желает сохранить изменения в таблице, приложение должно проверить, не были ли изменены данные с момента их считывания другими пользователями. Если данные были изменены, необходимо повторить операции считывания и модификации информации. Есть два стандартных способа, которые позволяют определить, производились ли изменения в записи данных:

  • простое сравнение считанных неизмененных значений полей с текущими значениями полей в таблице в операторе where;
  • использование в таблице поля типа timestamp и сравнение только его значений. Оптимистическая модель используется в приложениях с низким уровнем конкуренции между запросами.

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

Описание предметной области

Это было небольшое теоретическое вступление, а сейчас я расскажу о том, как, используя технологию доступа ADO.Net и язык программирования C#, разработать приложение, реализующее пессимистическую стратегию доступа к данным таблиц SQL Server. В качестве предметной области выберем систему продажи железнодорожных билетов, где без здорового пессимизма просто не обойтись. Моделирование данной предметной области не является целью статьи, поэтому модель будет существенно упрощена. Статья также не может служить руководством по технологии ADO.Net и языку C# - соответствующие учебные материалы есть на сайте msdn.microsoft.com. В качестве среды разработки будет использоваться Visual Studio.Net.

Рисунок 1. Сценарий работы приложения.

На Рисунке 1 приведен сценарий работы приложения покупки/продажи железнодорожного билета.

Для хранения информации о пассажирских местах создадим таблицу TrainSeats в базе данных Pubs и заполним ее тестовыми данными (см. Листинг 1).

Для каждого продаваемого места в таблице создается одна запись. Факт продажи билета определяется значением поля SaleDate, не равным null.

Описание логики работы приложения

Экран 1. Форма «Cписок свободных мест и оформление билета».
На начальном этапе необходимо получить список свободных мест на указанные пассажиром дату отправления и номер поезда. Соответствующая форма показана на Экране 1.

Рассмотрим код, реализующий получение списка свободных мест (кнопка «Получить список свободных мест»), который представлен в Листинге 2.

Открываем соединение conn (класс SqlConnection) на локальном сервере с базой данных Pubs в интегрированном режиме секретности. Создаем объект da (класс SqlDataAdapter) для чтения данных и задаем значения для его параметров. Следует обратить внимание на параметр readpast - считывается только информация о местах, которые не находятся в стадии оформления (не заблокированы). Методом Fill нужно заполнить объект ds (класс SqlDataSet) и привязать его к визуальному компоненту grFreeSeats (класс DataGrid). После этого соединение можно закрыть. Работа с данными происходит в отсоединенном, автономном (offline) режиме. Попутно замечу, что класс SqlDataSet является ключевым компонентом технологии ADO.Net. Это своего рода СУБД на клиентском компьютере, с которой можно работать, используя как реляционную, так и XML-иерархическую модель данных.

На втором этапе необходимо выбрать свободное место и приступить к оформлению билета (кнопка «Оформить билет»), как показано в Листинге 3.

Для работы с транзакциями в ADO.Net необходим объект trans (класс SqlTransaction). Этот объект в дальнейшем используется как указатель на транзакцию для выполнения команд. Блокирование выбранной записи производится с помощью объекта comm (класс SqlCommand). Обратите внимание на параметры: updlock - блокирование для изменения (другие пользователи не смогут редактировать эту запись), rowlock - блокирование ресурса на уровне записи, readpast - пропустить запись, если она заблокирована другим пользователем. Объект dr (класс SqlDa-taReader) позволяет узнать, насколько успешной была попытка блокирования - приложению должна вернуться только одна запись. Попытка блокирования может быть неудачной в случае одновременных действий нескольких операторов, оформляющих одно и то же место. В этом случае происходит откат ранее начатой транзакции и на экран выводится соответствующее сообщение.

Чтобы убедиться, что запись действительно заблокирована, можно выполнить хранимую процедуру sp_lock в среде Query Analyzer.

Замечание по классу SqlDataReader: это курсор на клиенте типа forward-only и read-only, который предъявляет минимальные требования к ресурсам клиентского ПК, так как в данный момент времени хранит в памяти только текущую запись. Обычно применяется для последовательной обработки очень большого числа записей на клиенте. В нашем случае для чтения информации можно было бы использовать и класс SqlDataSet.

Экран 2. Форма «Продажа билета».
При успешном блокировании начинается непосредственно этап продажи билета (см. Экран 2).

Код, реализующий действия при нажатии на кнопку «Продать билет», представлен в Листинге 4.

Через метод ExecuteNonQuery созданного ранее объекта comm отмечается факт продажи билета. ExecuteNon-Query возвращает число обработанных записей. В нашем случае выполнять такую проверку не требуется. Метод Commit объекта trans фиксирует транзакцию.

Если оператор нажимает на кнопку «Отказ», производится откат транз-акции:

private void buCancel_Click(object sender,
System.EventArgs e)
{
// откат транзакции
comm.Transaction.Rollback();
}

Заключение

Мы рассмотрели один из вариантов пессимистической модели одновременного доступа к информации, содержащейся в таблицах SQL Server. При этом использовались ADO.Net и С#. Разработчики, знакомые с технологией ADO, могут спросить: почему не были задействованы возможности блокирования записей, которые предоставляют серверные курсоры? Ответ прост - технология ADO.Net ориентирована в первую очередь на автономную работу с данными и попросту не поддерживает серверные курсоры. Возможно, такая поддержка появится в будущих версиях ADO.Net.

ИЛЬДАР ДАУТОВ - начальник отдела АКБ «Заречье» в Казани. Имеет сертификаты MCDBA и МСТ. С ним можно связаться по адресу: dia@mail.zarech.ru.