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

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

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

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

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

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

Решение 1. Использование вспомогательного регистра сведений и управляемых блокировок

Это наиболее надежный и профессиональный метод для получения строгой последовательности номеров (1, 2, 3...). Идея заключается в создании регистра сведений (например, СчетчикНомеров), где хранится последнее выданное значение.

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


// Создаем объект блокировки
Блокировка = Новый УправляемаяБлокировка;
ЭлементБлокировки = Блокировка.Добавить("РегистрСведений.СчетчикНомеров");
// Устанавливаем область блокировки (например, по типу заказа)
ЭлементБлокировки.УстановитьЗначение("ТипЗаказа", ТипЗаказа);
ЭлементБлокировки.Режим = РежимУправляемойБлокировки.Исключительный;

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

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

Решение 2. Составной номер (Идентификатор документа + Индекс строки)

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

Например, если документ «Поступление товаров и услуг» имеет номер «ПС-001», а мы обрабатываем 5-ю строку, номер заказа в регистре накопления может выглядеть как "ПС-001/5". Этот метод обладает рядом преимуществ:

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

В коде это реализуется в процедуре ОбработкаПроведения простым обходом табличной части:


Для Каждого СтрокаТЧ Из Товары Цикл
    НоваяЗапись = Движения.Заказы.Добавить();
    НоваяЗапись.НомерЗаказа = Строка(ЭтотОбъект.Номер) + "/" + Строка(СтрокаТЧ.НомерСтроки);
    // ... заполнение остальных реквизитов
КонецЦикла;

Решение 3. Использование Глобально Уникальных Идентификаторов (GUID)

В ситуациях, когда номер заказа нужен только как технический ключ для связи таблиц и не предназначен для чтения человеком, рекомендуется использовать встроенный механизм УникальныйИдентификатор. Это гарантирует 100% уникальность даже без обращения к базе данных и исключает любые конфликты в многопользовательской среде.

Посмотрим на пример генерации такого ключа:


ТехническийНомер = Строка(Новый УникальныйИдентификатор());
// Результат будет выглядеть примерно так: "550e8400-e29b-41d4-a716-446655440000"

Этот метод идеально подходит для интеграций с внешними системами или когда заказы создаются в распределенных базах данных (РИБ), где обычная числовая последовательность неизбежно приведет к коллизиям при синхронизации.

Решение 4. Временные метки (Миллисекунды)

Рассмотрим вариант, предложенный в обсуждении: использование префикса в сочетании с универсальной датой в миллисекундах. Это позволяет получить уникальное числовое значение, зависящее от времени.

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

Когда и где присваивать номер?

Разберем важный архитектурный момент: в каком событии лучше генерировать номер?

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

Подведем итог: выбор метода зависит от ваших требований. Если нужна строгая последовательность для отчетов — используйте Регистр сведений + Управляемые блокировки. Если нужна скорость и простота — используйте составной номер из данных документа. Если номер чисто технический — используйте GUID. Мы рекомендуем избегать блокировок всего регистра накопления, так как это критически снижает масштабируемость системы.

← На главную