БЛОКИРОВКИ ЗАПИСЕЙ

Представьте себе такую ситуацию, два пользователя одновременно получили доступ к одной и той же записи и начинают ее редактировать. Результат таких действий непременно станет неприятным сюрпризом как для одного, так и для другого пользователя. Для недопущения подобных ситуаций в OpenEdge ABL применяются блокировки записей. Всего таких блокировок, или как их еще называют - локировок (от англ. lock) существует 2 типа:

  • SHARE-LOCK - запись блокирована для изменения, но доступна для чтения;
  • EXCLUSIVE-LOCK - запись заблокирована как для изменения, так и для чтения.

Каждой читаемой из таблицы базы данных записи AVM, по умолчанию, автоматически выставляет блокировку SHARE-LOCK. Это означает, что другие пользователи в этот момент не смогут изменять данную запись, но при этом смогут ее прочитать.

FIND FIRST loan WHERE loan.branch-id EQ "0001". ← блокировка SHARE-LOCK 
DISPLAY loan.open-date. ← продолжает действовать SHARE-LOCK

Действие блокировки SHARE-LOCK распространяется до тех пор, пока запись находится в области видимости, т.е. пока она находится в буфере записи.

В момент когда запись переходит в статус редактирования, такой записи AVM автоматически повышает уровень блокировки до EXCLUSIVE-LOCK. В этот момент, данная запись становится недоступной другим пользователям как для изменения, так и для чтения с блокировкой SHARE-LOCK.

FIND FIRST loan WHERE loan.branch-id EQ "0001". ← блокировка SHARE-LOCK 
UPDATE loan.open-date. ← блокировка повышается до EXCLUSIVE-LOCK
DISPLAY loan.open-date ← продолжает действовать EXCLUSIVE-LOCK

Действие блокировки EXCLUSIVE-LOCK распространяется до завершения транзакции.

В приведенном примере, транзакционным блоком является вся процедура и поэтому действие блокировки EXCLUSIVE-LOCK будет распространяться до завершения процедуры.

Если по завершению транзакции запись продолжает быть доступной, т.е. остается в буфере записи, то блокировка такой записи автоматически понижается до SHARE-LOCK.

DO TRANSACTION:
   FIND FIRST loan WHERE loan.branch-id EQ "0001". ← блокировка SHARE-LOCK
   UPDATE loan.open-date. ← блокировка повышается до EXCLUSIVE-LOCK
END. ← блокировка понижается до SHARE-LOCK
DISPLAY loan.open-date ← продолжает действовать SHARE-LOCK

После того, как запись удаляется из буфера записи выставленная ей блокировка снимается. Освободить запись, работа с которой уже завершена, можно с помощью оператора RELEASE.

RELEASE record [NO-ERROR]

record - идентификатор буфера записи.

RELEASE осуществляет сохранение записи в таблицу и удаляет ее из буфера записи.

DO TRANSACTION:
   FIND FIRST loan WHERE loan.branch-id EQ "0001". ← блокировка SHARE-LOCK
   UPDATE loan.open-date. ← блокировка повышается до EXCLUSIVE-LOCK
END. ← блокировка понижается до SHARE-LOCK
DISPLAY loan.open-date ← продолжает действовать SHARE-LOCK

RELEASE (loan). ← снятие блокировки

Если другой пользователь, в момент редактирования записи первым пользователем, т.е. когда для нее установлена EXCLUSIVE-LOCK блокировка, попытается прочитать ее с SHARE-LOCK или EXCLUSIVE-LOCK блокировкой, ему будет выдано сообщение о том, что запись заблокирована и он будет вынужден либо прервать выполнение своей процедуры, либо ожидать пока первый пользователь завершит редактирование записи и ее блокировка сменится с EXCLUSIVE-LOCK на SHARE-LOCK, или будет полностью разблокирована.

loan in use by user-name on pts/6. Wait or press CTRL-C to stop. (121)

Аналогичная ошибка возникнет при попытке изменить, или прочитать запись с блокировкой EXCLUSIVE-LOCK, если другим пользователем ей уже установлена блокировка SHARE-LOCK.

Время ожидания разблокировки записи не является бесконечным и определяется стартовым параметром сессии -lkwtmo, значение которого по умолчанию составляет 30 минут, а минимально допустимое значение 60 секунд. Если второй пользователь примет решение ожидать когда первый пользователь отпустит заблокированную им запись и по истечению этого времени запись так и не будет освобождена, у второго пользователя возникнет событие STOP.

Использование FIND с опцией NO-WAIT позволит второму пользователю продолжить выполнение его процедуры не дожидаясь освобождения заблокированной записи.

При этом, так как запись не была все же прочитана (не помещена в буфер записи), то и обработана она быть не сможет.

FIND FIRST loan WHERE loan.branch-id EQ "0001" EXCLUSIVE-LOCK NO-WAIT.
IF AVAIL loan THEN
DO:
   ...
END.

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

Для этих целей в ABL применяется функция LOCKED, возвращающая логическое значение TRUE, когда искомая с помощью FIND ... NO-WAIT запись не доступна по причине наличия у нее конфликтующей блокировки установленной другим пользователем.

LOCKED record

где, record - буфер записи используемый в операторе FIND.

FIND FIRST loan WHERE loan.branch-id EQ "0001" EXCLUSIVE-LOCK NO-WAIT.
IF NOT AVAIL loan THEN
DO:
IF LOCKED loan THEN MESSAGE "Запись заблокирована другим пользователем".
ELSE MESSAGE "Запись не найдена".
END.

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

CAN-FIND
(
  [FIRST | LAST ] record [constant]
  [OF table] [WHERE expression ] [USE-INDEX index]
  [USING [FRAME frame] field
    [AND [FRAME frame] field] ...
  ]
  [SHARE-LOCK | NO-LOCK] [NO-WAIT] [NO-PREFETCH]

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

FIND FIRST loan WHERE loan.branch-id EQ "0001" NO-LOCK.

Использование NO-LOCK указывает на то, что никакой блокировки читаемой записи осуществляться не будет, т.е. не будет устанавливаться используемая по умолчанию блокировка SHARE-LOCK и запись останется доступной другим пользователям как для чтения, так и для изменения. Кроме этого, применение опции NO-LOCK позволит нам читать записи имеющие блокировку EXCLUSIVE-LOCK, но при этом есть риск прочитать частично измененные другим пользователем данные.

Никогда не следует читать запись с опцией NO-LOCK, если вы планируете ее изменять.

FIND FIRST loan WHERE loan.branch-id = "0001" NO-LOCK. ← блокировки нет
DO TRANSACTION:
UPDATE loan.open-date. ← блокировка повышается до EXCLUSIVE-LOCK
END. ← блокировка понижается до SHARE-LOCK
← продолжает действовать блокировка SHARE-LOCK

По завершению изменения записи, ее блокировка не будет снята, а будет понижена до SHARE-LOCK.

Далее рассмотрим как происходит расстановка блокировок записей в итерационных блоках, на примере FOR EACH.

FOR EACH loan WHERE loan.branch-id EQ "0001": ← блокировка SHARE-LOCK
   DISPLAY loan.open-date. ← продолжает действовать блокировка SHARE-LOCK
END. ← снятие блокировки

Блокировка записи устанавливается в начале итерации блока, и снимается по ее завершению.

FOR EACH loan WHERE loan.branch-id EQ "0001": ← блокировка SHARE-LOCK
   UPDATE loan.open-date. ← блокировка повышается до EXCLUSIVE-LOCK
END. ← снятие блокировки

Каждая итерация блока FOR в приведенном примере, является транзакцией, по завершению которой запись удаляется из буфера записей, в следствии чего, по завершению итерации блока блокировка записи снимается, а не понижается до SHARE-LOCK.

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

DO TRANSACTION:
FOR EACH loan WHERE loan.branch-id EQ "0001": ← блокировка SHARE-LOCK
   UPDATE loan.open-date. ← блокировка повышается до EXCLUSIVE-LOCK
END.
← все записи сохраняют блокировку EXCLUSIVE-LOCK
END. ← снятие всех блокировок

Аналогично оператору FIND, оператор FOR так же может использоваться с опцией NO-LOCK. 

FOR EACH loan WHERE loan.branch-id EQ "0001" NO-LOCK: 
   DISPLAY loan.open-date.
END. 

Рассмотрим еще один интересный пример. Два пользователя, одновременно, запускают выборки по таблице договоров (loan). Первый пользователь отбирает договора у которых loan.branch-id = "0001" с блокировкой EXCLUSIVE-LOCK, а второй пользователь отбирает договора у которых loan.branch-id = "0100" с блокировкой SHARE-LOCK.

процедура 1-го пользователя:

FOR EACH loan WHERE loan.branch-id = "0001" EXCLUSIVE-LOCK:
  DISPL loan.cont-code.
END.

процедура 2-го пользователя:

FOR EACH loan WHERE loan.branch-id = "0100":
  DISPL loan.cont-code.
END.

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

Немного изменим процедуру 1-го пользователя и повторим эксперимент:

FOR EACH loan WHERE CAN-DO("000*",loan.branch-id) EXCLUSIVE-LOCK:
  DISPL loan.cont-code.
END.

Теперь они стали мешаться друг другу, хотя запросы казалось бы по-прежнему не пересекаются. Причина такого поведения, как вы наверно смогли уже догадаться, кроется в использовании функции CAN-DO в условиях выборки. Почему так происходит?

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

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

Аналогичная ситуация могла бы возникнуть если бы первый пользователь вместо оператора FOR осуществлял бы поиск записи с помощью оператора FIND FIRST.

FIND FIRST loan WHERE CAN-DO("000*",loan.branch-id) EXCLUSIVE-LOCK:
IF AVAIL loan THEN
  DISPL loan.cont-code.

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

Описанная в самом начале статьи ситуация, когда два пользователя пытаются отредактировать одну и ту же запись все же возможна. Возникнуть она может тогда, когда два пользователя уже прочитали одну и ту же запись с блокировкой SHARE-LOCK и оба пытаются отредактировать ее, т.е. повысить уровень ее блокировки до EXCLUSIVE-LOCK. Никто из них не сможет этого сделать, так как каждому из них будет мешать выставленная другим пользователем блокировка SHARE-LOCK, и каждый из них получит сообщение о том, что запись уже заблокирована другим пользователем.

Все выставляемые SHARE-LOCK и EXCLUSIVE-LOCK блокировки учитываются в системной таблице _lock. Запись в этой таблице автоматически формируется для каждой записи, которой выставлена SHARE-LOCK или EXCLUSIVE-LOCK блокировка. Размер таблицы по умолчанию составляет 8192 записи и может быть изменен с помощью стартового параметра -L n (Lock Table Entries).  Минимально допустимый размер таблицы составляет 32 записи, а максимальный ограничивается доступной памятью системы. При этом, выставляемый размер таблицы должен быть кратным 32. Если указанное значение не является таковым, то OpenEdge автоматически изменит его на ближайшее большее к нему значение кратное 32.

Переполнение системной таблицы блокировок приведет к возникновению системной ошибки:

SYSTEM ERROR: Record lock table too small. Increase -L parameter.

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

Подведем итоги всего сказанного выше. Если вам необходимо только прочитать записи и никаких их изменений вы не планируете, то в данном случае поиск и выборка записей должны осуществляться с опцией NO-LOCK. В случае, когда необходимо прочитать запись и быть уверенным в том, что прочитанные данные не будут изменены до завершения процедуры или блока, то стоит применять блокировку SHARE-LOCK, отставляя тем самым другим пользователям возможность читать эти же записи. Если же вы отбираете записи с целью их модификации, то поиск и выборку таких записей следуют осуществлять с опцией EXCLUSIVE-LOCK.


   FIND FIRST loan WHERE loan.branch-id EQ "0001". ← блокировка SHARE-LOCK
Вы здесь: Главная Основы ABL БЛОКИРОВКИ ЗАПИСЕЙ