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

Программист 1С v8.3 (Обычные формы) Управленческий учет Промышленность, строительство и АПК
← На главную

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

Причина возникновения проблемы: Состояние гонки (Race Condition)

Разберем классический сценарий, описанный в вопросе. Программа использует функцию ПолучитьНовыйКодДляРегистраСведений. Внутри этой функции обычно выполняется запрос, который ищет максимальное значение кода в таблице и прибавляет к нему единицу (МАКСИМУМ(Код) + 1).

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

  1. Пользователь А выполняет запрос на получение максимального кода. База возвращает, например, «100».
  2. Пользователь Б в ту же миллисекунду выполняет такой же запрос. База все еще возвращает «100», так как Пользователь А еще не успел записать свои данные.
  3. Пользователь А прибавляет единицу, получает «101» и записывает данные.
  4. Пользователь Б тоже прибавляет единицу, получает «101» и пытается записать данные.

В этот момент возникает конфликт. Если код является измерением регистра, система выдаст ошибку уникальности. Если же код — это просто ресурс или реквизит, вы получите «задвоение» номера в базе данных. Важно понимать, что обычные запросы (SELECT) в режиме управляемых блокировок не блокируют данные для чтения другими пользователями, что и создает «окно» для дублирования.

Решение 1: Использование управляемых блокировок (DataLock)

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

Рассмотрим пример кода, который нужно внедрить в процедуру создания записи:


ПланыВидовХарактеристик.Номенклатура.СоздатьЭлемент(); // Пример для контекста
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("РегистрСведений.СоставКонтейнера_Производство");
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;

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

Важный нюанс: Блокировка всего регистра может замедлить работу, если записи создаются очень часто. Однако, если мы блокируем только по пустому набору измерений (как в примере), мы фактически создаем «очередь» на запись в этот регистр.

Решение 2: Использование вспомогательного объекта (Счетчика)

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

Алгоритм действий будет следующим:

  1. Находим в справочнике Нумераторы запись «СоставКонтейнера».
  2. Устанавливаем исключительную блокировку только на этот элемент справочника.
  3. Считываем текущее значение, увеличиваем его на 1, записываем обратно в справочник.
  4. Используем полученное число для записи в основной регистр.

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

Решение 3: Обработка исключения и повторная попытка

Это «оптимистичный» способ. Мы предполагаем, что задвоения случаются редко, и просто пробуем выполнить операцию еще раз, если получили ошибку. Посмотрим на реализацию:


УспешноЗаписано = Ложь;
Попытки = 0;

Пока Не УспешноЗаписано И Попытки < 3 Цикл
    Попытки = Попытки + 1;
    НовыйКод = УправлениеРозничнойТорговлей.ПолучитьНовыйКодДляРегистраСведений("СоставКонтейнера_Производство");
    
    Запись = РегистрыСведений.СоставКонтейнера_Производство.СоздатьМенеджерЗаписи();
    Запись.Код = НовыйКод;
    // ... заполнение ...
    
    Попытка
        Запись.Записать(Истина); // Запись с замещением или проверка уникальности
        УспешноЗаписано = Истина;
    Исключение
        // Если ошибка вызвана неуникальностью кода, цикл уйдет на следующую итерацию
        // и получит уже следующий свободный код.
    КонецПопытки;
КонецЦикла;

Важно: Этот метод менее надежен в высоконагруженных системах, так как он не устраняет причину (одновременное чтение), а лишь борется с последствиями.

Решение 4: Переход на использование GUID

Если для бизнес-логики не принципиально, чтобы код был коротким числом (например, 1, 2, 3...), самым современным и производительным решением будет использование УникальныйИдентификатор.

Проанализируем преимущества:

Пример использования GUID в качестве кода:


Запись = РегистрыСведений.СоставКонтейнера_Производство.СоздатьМенеджерЗаписи();
Запись.Код = Строка(Новый УникальныйИдентификатор());
// ... заполнение данных ...
Запись.Записать();

Если же код должен быть представлен в виде штрихкода (EAN13 и т.д.), то GUID не подойдет. В этом случае необходимо вернуться к Решению 1 с использованием транзакционной блокировки.

Выводы и рекомендации

Подведем итоги нашего анализа. Для надежной работы нумерации в регистрах сведений необходимо соблюдать два правила:

  1. Всегда оборачивайте процесс «Получение кода + Запись» в одну транзакцию.
  2. Используйте объект БлокировкаДанных с режимом Исключительный перед тем, как выполнять запрос к базе на получение максимального номера.

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

← На главную