Как предотвратить дублирование номеров документов с помощью управляемых блокировок в 1С

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

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

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

Почему стандартная блокировка документа не работает при создании номера

Разберем типичную ситуацию, с которой сталкиваются начинающие разработчики. При установке точки останова в процедуре присвоения номера кажется, что система должна заблокировать таблицу документов. Однако в управляемом режиме блокировок платформа ведет себя иначе. Пока новый документ не записан в базу данных, у него нет ссылки (Ref), а значит, заблокировать конкретный объект невозможно. Блокировка всей таблицы — слишком «дорогая» операция, которая парализует работу всех пользователей. Поэтому простое включение управляемого режима в конфигурации не решает проблему конкурентного доступа к счетчику номеров.

Стратегия решения: Использование вспомогательного регистра

Для реализации надежной нумерации мы проанализируем наиболее эффективный метод — использование вспомогательного Регистра сведений (например, ПоследниеНомераДокументов). Этот регистр будет хранить текущее значение последнего выданного номера в разрезе необходимых измерений (Организация, Тип документа и т.д.).

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

Шаг 1. Создание структуры для хранения номеров

Создадим непериодический независимый регистр сведений ОчередьНумерации со следующими реквизитами:

  1. Измерения: ТипДокумента (Строка или перечисление), Организация (СправочникСсылка.Организации).
  2. Ресурсы: ТекущийНомер (Число).

Шаг 2. Программная установка управляемой блокировки

Для того чтобы гарантировать, что только один сеанс в конкретный момент времени вычисляет новый номер, нам понадобится объект БлокировкаДанных. Очень важно выполнять эти действия внутри транзакции. Рассмотрим пример кода, который можно разместить в модуле объекта документа в событии ПередЗаписью или в специализированном общем модуле.

Проанализируем пример реализации:


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

КонецФункции

Шаг 3. Интеграция в процесс записи документа

Выясним, где лучше всего вызывать этот код. Хотя платформа предоставляет событие ПриУстановкеНовогоНомера, для сложных алгоритмов с блокировками лучше использовать ПередЗаписью. Это связано с тем, что событие установки номера может вызываться системой несколько раз (например, при интерактивном открытии формы), что приведет к неоправданным ожиданиям блокировок или «растрате» номеров.

Рассмотрим пример вызова в модуле документа:


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

Важные нюансы управляемых блокировок

Разберем несколько критических моментов, которые обеспечат стабильность системы:

  1. Режим блокировки: Мы используем РежимБлокировкиДанных.Исключительный. Если использовать Разделяемый, то два пользователя смогут одновременно прочитать данные (оба увидят номер 10) и оба попытаются записать номер 11, что приведет к ошибке Deadlock или нарушению логики.
  2. Область транзакции: Блокировка, установленная методом Заблокировать(), действует до конца текущей транзакции. Как только документ будет записан или произойдет отказ от записи, блокировка с регистра будет снята автоматически. Нет необходимости (и возможности) вызывать «Разблокировать» вручную для управляемых блокировок внутри транзакции.
  3. Таймауты: Если один пользователь «зависнет» на этапе вычисления номера (например, из-за медленного запроса), второй пользователь будет ждать. Время этого ожидания регулируется параметром Время ожидания блокировки данных в настройках сервера 1С или параметрами СУБД. По умолчанию это обычно 20 секунд.

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

Проанализируем ситуацию: что произойдет, если номер в регистре увеличился, но запись документа сорвалась (например, не заполнено обязательное поле)? В этом случае транзакция откатится, и запись в регистре сведений ОчередьНумерации также вернется к прежнему значению. Это преимущество использования транзакционного механизма — у вас не будет пропусков в номерах из-за технических сбоев при записи.

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

Заключение

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

← На главную