При разработке сложных систем на платформе 1С:Предприятие 8 часто возникает задача реализации уникальной нумерации, которая не ограничивается стандартными механизмами платформы. Если в компании принята сложная структура номера (с префиксами организаций, суффиксами или специфическими корнями), стандартная автонумерация может не справляться. В условиях многопользовательской работы это неизбежно приводит к конфликтам: два пользователя одновременно нажимают кнопку «Записать», система генерирует для них один и тот же номер, и один из документов не записывается из-за ошибки уникальности.
Рассмотрим, как правильно использовать механизм управляемых блокировок для изоляции процесса присвоения номера и гарантированного исключения дублей.
Разберем типичную ситуацию, с которой сталкиваются начинающие разработчики. При установке точки останова в процедуре присвоения номера кажется, что система должна заблокировать таблицу документов. Однако в управляемом режиме блокировок платформа ведет себя иначе. Пока новый документ не записан в базу данных, у него нет ссылки (Ref), а значит, заблокировать конкретный объект невозможно. Блокировка всей таблицы — слишком «дорогая» операция, которая парализует работу всех пользователей. Поэтому простое включение управляемого режима в конфигурации не решает проблему конкурентного доступа к счетчику номеров.
Для реализации надежной нумерации мы проанализируем наиболее эффективный метод — использование вспомогательного Регистра сведений (например, ПоследниеНомераДокументов). Этот регистр будет хранить текущее значение последнего выданного номера в разрезе необходимых измерений (Организация, Тип документа и т.д.).
Суть метода заключается в том, что мы блокируем не сам документ, а конкретную запись в регистре, которая отвечает за нумерацию данного типа документов. Рассмотрим этот процесс по шагам.
Создадим непериодический независимый регистр сведений ОчередьНумерации со следующими реквизитами:
ТипДокумента (Строка или перечисление), Организация (СправочникСсылка.Организации).ТекущийНомер (Число).Для того чтобы гарантировать, что только один сеанс в конкретный момент времени вычисляет новый номер, нам понадобится объект БлокировкаДанных. Очень важно выполнять эти действия внутри транзакции. Рассмотрим пример кода, который можно разместить в модуле объекта документа в событии ПередЗаписью или в специализированном общем модуле.
Проанализируем пример реализации:
Функция ПолучитьНовыйНомер(ТекущаяОрганизация, ТипДокументаСтрокой) Экспорт
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("РегистрСведений.ОчередьНумерации");
// Устанавливаем фильтры, чтобы заблокировать только нужную нам ветку номеров
ЭлементБлокировки.УстановитьЗначение("Организация", ТекущаяОрганизация);
ЭлементБлокировки.УстановитьЗначение("ТипДокумента", ТипДокументаСтрокой);
// Устанавливаем Исключительный режим, чтобы другие сеансы ждали завершения нашей транзакции
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
// Начало критической секции
Блокировка.Заблокировать();
// Теперь мы внутри защищенного участка. Считываем текущий номер из регистра
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ОчередьНумерации.ТекущийНомер
|ИЗ
| РегистрСведений.ОчередьНумерации КАК ОчередьНумерации
|ГДЕ
| ОчередьНумерации.Организация = &Организация
| И ОчередьНумерации.ТипДокумента = &ТипДокумента";
Запрос.УстановитьПараметр("Организация", ТекущаяОрганизация);
Запрос.УстановитьПараметр("ТипДокумента", ТипДокументаСтрокой);
РезультатЗапроса = Запрос.Выполнить();
НовыйНомер = 1; // Значение по умолчанию
Если Не РезультатЗапроса.Пустой() Тогда
Выборка = РезультатЗапроса.Выбрать();
Выборка.Следующий();
НовыйНомер = Выборка.ТекущийНомер + 1;
КонецЕсли;
// Обновляем значение в регистре
МенеджерЗаписи = РегистрыСведений.ОчередьНумерации.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Организация = ТекущаяОрганизация;
МенеджерЗаписи.ТипДокумента = ТипДокументаСтрокой;
МенеджерЗаписи.ТекущийНомер = НовыйНомер;
МенеджерЗаписи.Записать();
Возврат НовыйНомер;
КонецФункции
Выясним, где лучше всего вызывать этот код. Хотя платформа предоставляет событие ПриУстановкеНовогоНомера, для сложных алгоритмов с блокировками лучше использовать ПередЗаписью. Это связано с тем, что событие установки номера может вызываться системой несколько раз (например, при интерактивном открытии формы), что приведет к неоправданным ожиданиям блокировок или «растрате» номеров.
Рассмотрим пример вызова в модуле документа:
Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
Если ЭтоНовый() И ЗначениеЗаполнено(Организация) Тогда
// Вызываем нашу функцию в транзакции записи
СледующийПорядковыйНомер = ПолучитьНовыйНомер(Организация, "ЗаказПокупателя");
ЭтотОбъект.Номер = СформироватьСтрокуНомера(СледующийПорядковыйНомер);
КонецЕсли;
КонецПроцедуры
Разберем несколько критических моментов, которые обеспечат стабильность системы:
РежимБлокировкиДанных.Исключительный. Если использовать Разделяемый, то два пользователя смогут одновременно прочитать данные (оба увидят номер 10) и оба попытаются записать номер 11, что приведет к ошибке Deadlock или нарушению логики.Заблокировать(), действует до конца текущей транзакции. Как только документ будет записан или произойдет отказ от записи, блокировка с регистра будет снята автоматически. Нет необходимости (и возможности) вызывать «Разблокировать» вручную для управляемых блокировок внутри транзакции.Время ожидания блокировки данных в настройках сервера 1С или параметрами СУБД. По умолчанию это обычно 20 секунд.Проанализируем ситуацию: что произойдет, если номер в регистре увеличился, но запись документа сорвалась (например, не заполнено обязательное поле)? В этом случае транзакция откатится, и запись в регистре сведений ОчередьНумерации также вернется к прежнему значению. Это преимущество использования транзакционного механизма — у вас не будет пропусков в номерах из-за технических сбоев при записи.
Однако помните, что если пользователь удалит уже записанный документ, «дырка» все равно появится. Решение проблемы сплошной нумерации при удалении — это отдельная сложная задача, требующая пересчета всех последующих номеров или повторного использования удаленных, что крайне не рекомендуется в бухгалтерском учете.
Мы рассмотрели механизм работы управляемых блокировок на примере задачи нумерации. Главный вывод: блокируйте не объект, а ресурс. Создание виртуального «узла блокировки» в виде записи регистра сведений позволяет гибко и производительно управлять дочередностью доступа к общим данным, предотвращая конфликты в высоконагруженных системах.