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

Программист 1С v8.3 (Обычные формы) IT и автоматизация бизнеса
← На главную

В практике разработки на платформе 1С:Предприятие 8 часто возникает задача создания сквозной нумерации, которая объединяла бы различные типы объектов: справочники, документы разных видов и, возможно, бизнес-процессы. Основная сложность этой задачи заключается не в самом получении следующего числа, а в обеспечении уникальности и параллельности работы пользователей. Если несколько человек одновременно записывают документы, система должна выдать каждому уникальный номер, не заставляя остальных ждать завершения длинных транзакций (проведения, расчетов и т.д.).

Рассмотрим подробнее причины возникновения проблем с блокировками и разберем наиболее эффективные способы реализации единого нумератора.

Анализ проблемы: Почему возникают блокировки?

Когда мы пытаемся реализовать нумератор через РегистрСведений или специальную таблицу, алгоритм обычно выглядит так: в транзакции записи объекта мы считываем текущее максимальное значение, прибавляем единицу и записываем новое значение обратно. В этот момент платформа устанавливает блокировку на запись в регистре. Если этот процесс происходит внутри транзакции записи документа, блокировка будет удерживаться до самого конца — пока документ не запишется и не проведется.

Проанализируем ситуацию: если проведение документа занимает 5–10 секунд (из-за сложных расчетов по регистрам бухгалтерии или накопления), то все остальные пользователи, которым нужен новый номер, будут «висеть» в ожидании. Это создает «узкое горлышко» в системе. Нам необходимо разорвать связь между получением номера и длительным процессом записи основного объекта.

Решение №1: Использование вспомогательного справочника

Одним из изящных решений является использование отдельного справочника (например, с именем ЭлектронныеКоды) в качестве генератора последовательности. Платформа 1С очень эффективно умеет нумеровать элементы справочников самостоятельно.

Разберем этот метод по шагам:

  1. Создаем справочник ЭлектронныеКоды. В его настройках устанавливаем тип кода «Число» и нужную длину.
  2. В объектах, которые требуют единой нумерации, добавляем реквизит ЭлектронныйКод (тип Число).
  3. В обработчике события ПередЗаписью или ОбработкаЗаполнения реализуем логику создания нового элемента вспомогательного справочника.

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


Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
    Если ЭтоНовый() И НЕ ЗначениеЗаполнено(ЭтотОбъект.ЭлектронныйКод) Тогда
        // Создаем новый элемент во вспомогательном справочнике
        НовыйКодОбъект = Справочники.ЭлектронныеКоды.СоздатьЭлемент();
        НовыйКодОбъект.Записать(); // Здесь платформа сама назначит следующий уникальный номер
        
        // Присваиваем полученный номер нашему основному объекту
        ЭтотОбъект.ЭлектронныйКод = Число(НовыйКодОбъект.Код);
    КонецЕсли;
КонецПроцедуры

Важный нюанс: Чтобы минимизировать блокировки, запись вспомогательного справочника должна происходить максимально быстро. Однако, если вызвать Записать() внутри основной транзакции документа, блокировка все равно будет длиться до завершения проведения. Чтобы этого избежать, получение номера нужно выносить в отдельную транзакцию.

Решение №2: Выделенная транзакция для получения номера («Метод билета»)

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

Рассмотрим, как это реализовать программно:


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

Эту функцию следует вызывать, например, в обработчике ОбработкаЗаполнения или непосредственно перед открытием формы нового объекта. Таким образом, номер будет уже вписан в реквизит объекта до того, как пользователь нажмет кнопку «Провести».

Сравнение методов и проблема «дырок» в нумерации

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

  1. Если используется выделенная транзакция: Номер получается сразу. Если пользователь закроет форму документа без сохранения или если основная запись сорвется по ошибке, этот номер будет «потерян». В последовательности возникнет пропуск (1, 2, 4, 5...). Для управленческого учета это обычно не критично, но для строгой финансовой отчетности может быть недопустимо.
  2. Если номер получается внутри основной транзакции: Пропусков не будет, так как при ошибке записи откатится и получение номера. Но в этом случае вы возвращаетесь к проблеме блокировок, с которой начали.

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

Рекомендации по реализации

Подводя итог, для создания качественного единого нумератора рекомендуем придерживаться следующих правил:

Использование предложенных методов позволит вам создать надежную систему идентификации объектов, сохранив при этом высокую производительность системы 1С.

← На главную