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