Блокировка данных. Пример
Управляемые формы. Платформа 1С 8.3
Объект конфигурации Нумераторы в дереве документов обеспечивает порядок нумерации стандартного реквизита документов – Номер .
Для обеспечения порядка нумерации других реквизитов документа типа число или строка приходится создавать собственные нумераторы ( или счетчики) в виде регистра сведений.
Например, в одной конфигурации создан вид документа
“Страховой полис”, который кроме стандартного реквизита номер , содержит еще реквизит Номер полиса , порядок нумерации которого от контрагента ( исполнителя) документа ( страховой компании).
Для обеспечения уникальности нумерации реквизита Номер полиса в разрезе контрагента, я создал периодический в приделах года, регистр сведения Нумератор Полиса . Этот регистр содержал одно измерение – Контрагент и один ресурс – Номер
Однако, были случай, когда при интенсивном создании документа полиса на основании документа Заказ покупателя без блокировки нумератора создавались дублированные номера полиса. Причина была в том, что новый номер не успевал записаться в регистре сведения Нумератор полиса до того, как поступал новый запрос на получение очередного номера полиса.
Причина кажется не вероятно !.Но факт есть факт. Особенно при сильно загрузке сервера нумератор раздавал один и тот же номер разным полисам потому, что он не успевал его записывать.
Использование метод глобального контекста перед чтением данных нумератора
ЗаблокироватьДанныеДляРедактирования(<Ключ>, <ВерсияДанных>, <ИдентификаторФормы>)
не дает положительный результат, так как этот метод
блокирует данные только для редактирования в управляемой форме, а не для чтения.
Проблема была решена после блокировки счетчика перед чтением с помощью объекта блокировкаДанных:
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("РегистрСведений._НумерацияДоговоровСтраховыхКомпаний");
Блокировка.Заблокировать();
Средствами встроенного языка установка управляемых блокировок внутри явной или скрытой (неявной) транзакции происходит с помощью специального объекта БлокировкаДанных, описание доступных свойств и методов которого можно посмотреть в синтакс-помощнике в ветви Общие объекты ( См рис 1).

Объект блокировкаДанных предназначен для явной блокировки данных от чтения или изменения другими сессиями. Метод заблокировать() устанавливает управляемые блокировки и ждет, когда это возможно – когда блокировка в другой сессии будет снята . По истечению времени ожидания возникает прерывание системы и вызывается ошибка СУБД.
Отмена ( снятие) блокировки происходит автоматически при выходе из транзакции . У данного объекта нет метода типа отмены или снятия блокировки.
Другие примеры блокировки данных из встроенного языка можно посмотреть по ссылке здесь .
В этой статье я рассматриваю следующий практический пример:
Перед записью документа Полис требуется заблокировать Нумератор полиса , если это удалось, модифицировать документ Полис – присвоить очередной номер полиса . В противном случае – проинформировать пользователя об отказе в выполнении операции записи полиса такого вида:
Не удалось заблокировать нумератор полиса. Документ полис не может быть записан . Повторите попытку!
Решение этого примера выполняется в теле обработчика события ПередЗаписьюНаСервере.
Данное событие вызывается перед записью полиса на сервере и выполняется вне транзакции. Можно отменить запись полиса если в теле процедуры-обработчика установить отказ = истина.
&НаСервере
Процедура ПередЗаписьюНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи)
Если Не ЗначениеЗаполнено(ТекущийОбъект.НомерПолиса) Тогда
НомерПолиса = ПолучитьНомерПолиса (ТекущийОбъект.Контрагент);
Если НомерПолиса = 0 Тогда
Отказ = Истина;
Сообщить(" Не удалось записать полис!. Повторите попытку");
Иначе
ТекущийОбъект.НомерПолиса = НомерПолиса;
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Обратите внимание, что вызов функции
ПолучитьНомерПолиса происходит если номер полиса не заполнен. Кроме того, запись полиса отменяется если она возвращает ноль при ее вызове .
Весь программный код можно было бы поместить в модуле объекта в обработчике события ПередЗаписью , а не в модуле управляемой формы полиса.
Программный код пользовательской функции , возвращающей номер полиса и осуществляющей чтение и запись с блокировкой нумератора полиса показан в следующем фрагменте:
&НаСервере
Функция ПолучитьНомерПолиса (Контрагент)
НачатьТранзакцию();
Попытка
Отбор = Новый Структура;
Отбор.Вставить("Контрагент", Контрагент);
КлючЗаписи = РегистрыСведений._НумераторПолиса.СоздатьКлючЗаписи(Отбор);
ЗаблокироватьДанныеДляРедактирования(КлючЗаписи);
// БлокировкаДанных Предназначен для явной блокировки данных от чтения // или изменения другими сессиями.
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("РегистрСведений._НумерацияДоговоровСтраховыхКомпаний");
Блокировка.Заблокировать();
Номер=0;
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ
|_НумераторПолисаСрезПоследних.Номер КАК Номер
|ИЗ
|РегистрСведений._НумераторПолиса.СрезПоследних(, Контрагент = &Контрагент) КАК _НумераторПолисаСрезПоследних ;
Запрос.УстановитьПараметр("Контрагент", Контрагент);
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
Номер = Выборка.Номер;
КонецЕсли;
Номер = Номер+1;
запись = РегистрыСведений._НумераторПолиса.СоздатьМенеджерЗаписи();
запись.Контрагент = Контрагент;
запись.Период = ТекущаяДата();
запись.Номер =Номер;
Запись.Записать();
РазблокироватьДанныеДляРедактирования(КлючЗаписи);
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
Сообщить( «Не удалось заблокировать нумератор. Он уже заблокирован";
Возврат 0;
КонецПопытки;
Возврат Номер;
КонецФункции
Обратите внимание, что блокировка записи происходит до выполнения запроса о чтении номера. Если запись заблокирована вызывается исключение и функция возвращает 0.
Часто блокирование и разблокирование происходит внутри транзакции. Причем, транзакция начинается перед блокированием данных и зафикируется после разблокирования . Вот пример из библиотеки стандартных подсистем при старте отложенного бизнес-процесса
Процедура СтартоватьОтложенныйПроцесс(БизнесПроцесс) Экспорт
НачатьТранзакцию();
Попытка
ЗаблокироватьДанныеДляРедактирования(БизнесПроцесс);
БизнесПроцессОбъект = БизнесПроцесс.ПолучитьОбъект();
// Стартуем бизнес процесс и регистрируем этот старт в регистре.
БизнесПроцессОбъект.Старт();
РегистрыСведений.ПроцессыДляЗапуска.ЗарегистрироватьСтартПроцесса(БизнесПроцесс);
РазблокироватьДанныеДляРедактирования(БизнесПроцесс);
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ОписаниеОшибки = ПодробноеПредставлениеОшибки(ИнформацияОбОшибке());
ТекстОшибки = НСтр("ru = 'Во время отложенного старта этого процесса произошла ошибка:
|%1
|Попробуйте запустить процесс вручную, а не отложенно.'");
Описание = СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(
ТекстОшибки,
ОписаниеОшибки);
РегистрыСведений.ПроцессыДляЗапуска.ЗарегистрироватьОтменуСтарта(БизнесПроцесс, Описание);
КонецПопытки;
КонецПроцедуры