Защита от потерянных обновлений

Материал из Course Orchestra
Перейти к: навигация, поиск
Внимание! Вы просматриваете документацию к Celesta 6.x. Документация по Celesta 7.x доступна на courseorchestra.github.io/celesta.

1. Справочник Celesta

1.1 Введение и основные понятия
1.2 Запуск и авто-обновление
1.3 Базовая настройка
1.4 Системные таблицы
1.5 CelestaSQL
1.6 CelestaDoc
1.7 Контексты сессии и вызова
1.8 Курсоры
1.9 BLOB-поля
1.10 Option-поля
1.11 Защита от потерянных обновлений
1.12 Метаданные Celesta
1.13 CelestaUnit

2. Celesta и базы данных

2.1 Особенности работы Celesta с поддерживаемыми типами СУБД
2.2 Проектирование базы данных Celesta в DBSchema

3. Создание решений с использованием Celesta для ShowCase

3.1 Программа обучения Celesta
3.2 Подготовка рабочего места для работы с Celesta
3.2.1 Для разработчиков платформы
3.2.2 Для разработчиков решений
3.3 Системные гранулы Celesta
3.3.1 common
3.3.1.1 Экспорт/импорт данных
3.3.1.2 Навигатор
3.3.1.3 Серии номеров
3.3.1.4 Иерархия Дьюи
3.3.1.5 Системные функции
3.3.1.6 Реестр настроек
3.3.1.7 Mailsender
3.3.1.8 Common.filter
3.3.2 common.api
3.3.4 security
3.3.3 lyra
3.4 Стандартные гранулы Celesta
3.4.1 dirusing
3.4.2 workflow
3.4.3 File repository
3.5 Отрисовка элементов Showcase при помощи Celesta
3.5.1 Конвертер XML-JSON
3.5.2 Навигатор (Navigator)
3.5.3 Информационная панель (Datapanel)
3.5.4 Серверное действие (Server activity)
3.5.5 Вебтекст (WebText)
3.5.6 Грид (Grid)
3.5.6.1 Панель инструментов (ToolBar)
3.5.7 XForms
3.5.7.1 Селекторы
3.5.7.2 Submission
3.5.7.3 Загрузка/Выгрузка файлов (Upload/Download)

5. Решение проблем

5.1 Проблемы с кодировкой jython-файлов

Что такое потерянное обновление (lost update)?

Чтобы проиллюстрировать понятие "потерянное обновление", рассмотрим следующий сценарий.

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

  1. Пользователь А открывает карточку клиента для того, чтобы отредактировать его почтовый индекс.
  2. Пользователь Б независимо от пользователя А на своей машине открывает карточку клиента для того, чтобы отредактировать значение его кредитного лимита.
  3. Пользователь Б, изменив значение поля "кредитный лимит", сохраняет карточку. В базу данных записалась информация от пользователя Б, но пользователь А продолжает работу со своей копией карточки, где значение поля "кредитный лимит" ещё не обновилось.
  4. Пользователь А заканчивает редактировать почтовый индекс клиента и сохраняет свою копию карточки. В базу данных сохраняются все поля карточки, в том числе старый кредитный лимит.

В итоге получается, что работа пользователя Б потеряна!

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

rec1 = FooTableCursor(context)
rec2 = FooTableCursor(context)

Пусть в некий момент оба курсора получают данные об одной и той же записи, например, таким образом:

rec1.get(1)
rec2.copyFieldsFrom(rec1)

Допустим, запись таблицы FooTable с id = 1 состоит всего из трёх полей:

id field1 field2
1 oldvalue oldvalue

И пусть теперь оба курсора выполняют модификации записи:

rec1.field1 = 'newvalue'
rec1.update() 

#в rec1 и в базе данных уже запись 1 | newvalue | oldvalue

#но в rec2 всё ещё                 1 | oldvalue | oldvalue

rec2.field2 = 'newvalue'
rec2.update() 

#в базе данных теперь будет запись 1 | oldvalue | newvalue ???

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

Способы защиты от потерянных обновлений

Исторически для борьбы с явлением потерянного обновления сложились два метода:

  • Метод пессимистической блокировки (pessimistic lock) заключается в том, что при начале редактирования записи из какого-либо места в приложении, запись помечается как заблокированная, и никакой другой скрипт или пользователь не сможет начать редактирование до тех пор, пока предыдущий редактор не завершит свою работу, обновив запись или отказавшись от её редактирования.
  • Метод оптимистической блокировки (optimistic lock) заключается в том, что любому пользователю или скрипту в любое время разрешено начать редактирование записи, при этом в момент извлечения записи из базы данных извлекается и номер версии записи. В момент сохранения записи происходит проверка: если номер сохраняемой версии совпадает с таковым в базе данных, запись сохраняется и номер версии записи в базе данных инкрементируется; если же номер сохраняемой версии меньше, чем номер версии записи в базе данных, то пользователю выдаётся ошибка с сообщением о том, что кто-то поменял запись прежде и совет прочитать запись заново.

Каждый из методов, разумеется, имеет свои недостатки.

Главным недостатком оптимистической блокировки является, конечно, то, что пользователю не удастся записать результат своей работы в базу данных, если кто-то успел выполнить обновление той же самой записи прежде. Тем не менее, на практике это происходит в довольно редких случаях, и "страдают" от этого в основном лишь самые "нерасторопные" пользователи, у которых слишком большое время проходит от извлечения записи до завершения её редактирования.

Главным недостатком пессимистической блокировки является то, что от пользователя ожидается, что, начав редактирование записи, он явным образом завершит редактирование или откажется от него, сняв с записи блокировку. Однако на практике, если блокировка записи продолжается слишком долго, невозможно понять, какой из случаев имеет место:

  1. пользователь продолжает активную и сложную работу с записью и нужно ждать завершения его работы?
  2. пользователю уже не нужно редактировать запись, но он просто забыл нажать на кнопку "отмена" и ушёл обедать/отправился в отпуск на две недели/уволился из организации?
  3. у пользователя разорвалась связь с сервером/завис клиент/перезагрузился компьютер?

Во втором и третьем случаях требуется внешнее вмешательство и явное снятие блокировки силами администраторов, иначе другие пользователи не смогут работать с заблокированной записью. Случай 2 никогда нельзя исключать в организации, где работают живые люди, а случай 3 особенно вероятен в условиях клиент-серверной работы в Web-среде.

В целом для систем, подобных Celesta, недостатки пессимистической блокировки являются гораздо более существенными, чем недостатки оптимистической блокировки, и поэтому Celesta использует метод оптимистической блокировки для борьбы с потерянными обновлениями.

Защита от потерянных обновлений в Celesta

По умолчанию, всякая таблица в системе Celesta снабжается системным полем recversion с типом INT NOT NULL.

Данное поле создаётся автоматически, разработчику не следует включать это поле в CREATE TABLE-скрипт. Более того, разработчик не может создать собственное поле с именем recversion. Доступ к этому полю имеется через классы доступа к данным, как к обычному полю.

При вставке новой записи поле recversion принимает значение по умолчанию 1 (единица).

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

 Can not update <имя гранулы и таблицы> ([<значения полей первичного ключа>]): this record has been already modified by someone. 
 Please start updating again.

В двух рассмотренных выше примерах Celesta выдаст ошибку и не даст отправить в базу данных запись, приводящую к потерянным обновлениям.

Иногда возникает необходимость отказаться от защиты от потерянных обновлений — например, если нет желания поддерживать системное поле recversion и специальные триггеры. В этом случае при создании таблицы на языке CelestaSQL необходимо использовать опцию WITH NO VERSION CHECK после определения таблицы.