При разработке систем учета в 1С часто возникает задача присвоения уникальных номеров объектам, которые не являются стандартными документами или справочниками с автонумерацией. Например, когда нужно пронумеровать строки внутри документа или записи в регистре накопления, возникающие при проведении поступления товаров. Основная сложность заключается в обеспечении уникальности: если два пользователя одновременно проводят документы, система может выдать им один и тот же «максимальный» номер, что приведет к дублированию данных. В этой статье мы разберем, как правильно организовать процесс нумерации, избежав коллизий и падения производительности.
Рассмотрим стандартную ситуацию: программа считывает максимальный номер из базы данных, прибавляет к нему единицу и записывает новый результат. Если в этот короткий промежуток времени (между чтением и записью) другой пользователь выполнит те же действия, он получит тот же самый «максимальный» номер. В итоге мы получим две записи с одинаковыми идентификаторами.
Попытка просто «заблокировать» регистр накопления целиком — это избыточное решение, которое парализует работу всех пользователей. Выясним причину: регистры накопления оптимизированы для записи оборотов и остатков, а не для управления последовательностью номеров. Для решения этой задачи нам потребуются другие инструменты.
Это наиболее надежный и профессиональный метод для получения строгой последовательности номеров (1, 2, 3...). Идея заключается в создании регистра сведений (например, СчетчикНомеров), где хранится последнее выданное значение.
Проанализируем алгоритм работы в коде. Нам необходимо наложить исключительную блокировку на запись в этом регистре, чтобы другие транзакции ждали своей очереди. Разберем пример кода для получения следующего номера:
// Создаем объект блокировки
Блокировка = Новый УправляемаяБлокировка;
ЭлементБлокировки = Блокировка.Добавить("РегистрСведений.СчетчикНомеров");
// Устанавливаем область блокировки (например, по типу заказа)
ЭлементБлокировки.УстановитьЗначение("ТипЗаказа", ТипЗаказа);
ЭлементБлокировки.Режим = РежимУправляемойБлокировки.Исключительный;
// Начало транзакции (если код не внутри ОбработкиПроведения)
НачатьТранзакцию();
Попытка
Блокировка.Заблокировать();
// Читаем текущее значение
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ ПоследнийНомер ИЗ РегистрСведений.СчетчикНомеров ГДЕ ТипЗаказа = &ТипЗаказа";
Запрос.УстановитьПараметр("ТипЗаказа", ТипЗаказа);
Результат = Запрос.Выполнить();
Если Результат.Пустой() Тогда
НовыйНомер = 1;
Иначе
Выборка = Результат.Выбрать();
Выборка.Следующий();
НовыйНомер = Выборка.ПоследнийНомер + 1;
КонецЕсли;
// Записываем обновленное значение обратно
МенеджерЗаписи = РегистрыСведений.СчетчикНомеров.СоздатьМенеджерЗаписи();
МенеджерЗаписи.ТипЗаказа = ТипЗаказа;
МенеджерЗаписи.ПоследнийНомер = НовыйНомер;
МенеджерЗаписи.Записать();
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ВызватьИсключение "Не удалось получить номер заказа, попробуйте еще раз.";
КонецПопытки;
Важный нюанс: использование управляемой блокировки гарантирует, что пока одна сессия увеличивает счетчик, вторая будет ждать на методе Заблокировать(). Это исключает дубли, но требует грамотной обработки исключений.
Если нам не принципиальна красивая последовательность цифр (1, 2, 3...), а важна лишь уникальность идентификатора в рамках системы, рассмотрим более простой способ. Мы можем формировать номер как комбинацию номера первичного документа и номера строки его табличной части.
Например, если документ «Поступление товаров и услуг» имеет номер «ПС-001», а мы обрабатываем 5-ю строку, номер заказа в регистре накопления может выглядеть как "ПС-001/5". Этот метод обладает рядом преимуществ:
В коде это реализуется в процедуре ОбработкаПроведения простым обходом табличной части:
Для Каждого СтрокаТЧ Из Товары Цикл
НоваяЗапись = Движения.Заказы.Добавить();
НоваяЗапись.НомерЗаказа = Строка(ЭтотОбъект.Номер) + "/" + Строка(СтрокаТЧ.НомерСтроки);
// ... заполнение остальных реквизитов
КонецЦикла;
В ситуациях, когда номер заказа нужен только как технический ключ для связи таблиц и не предназначен для чтения человеком, рекомендуется использовать встроенный механизм УникальныйИдентификатор. Это гарантирует 100% уникальность даже без обращения к базе данных и исключает любые конфликты в многопользовательской среде.
Посмотрим на пример генерации такого ключа:
ТехническийНомер = Строка(Новый УникальныйИдентификатор());
// Результат будет выглядеть примерно так: "550e8400-e29b-41d4-a716-446655440000"
Этот метод идеально подходит для интеграций с внешними системами или когда заказы создаются в распределенных базах данных (РИБ), где обычная числовая последовательность неизбежно приведет к коллизиям при синхронизации.
Рассмотрим вариант, предложенный в обсуждении: использование префикса в сочетании с универсальной датой в миллисекундах. Это позволяет получить уникальное числовое значение, зависящее от времени.
Хотя шанс совпадения миллисекунд у двух разных пользователей крайне мал, в высоконагруженных системах (где сотни пользователей одновременно нажимают кнопку «Провести») этот риск все же существует. Чтобы сделать этот метод надежнее, к дате стоит добавлять НомерСеанса пользователя. Это гарантирует, что даже в одну и ту же миллисекунду два разных сеанса получат разные значения.
Разберем важный архитектурный момент: в каком событии лучше генерировать номер?
ПередЗаписью: подходит, если номер должен видеть пользователь в форме документа. Но помните: если пользователь просто сохранит документ и не проведет его, или удалит потом — в нумерации возникнет «дырка» (пропуск).ОбработкаПроведения: лучшее место для технических номеров, предназначенных для регистров накопления. Номера будут присваиваться только реально проведенным операциям.Подведем итог: выбор метода зависит от ваших требований. Если нужна строгая последовательность для отчетов — используйте Регистр сведений + Управляемые блокировки. Если нужна скорость и простота — используйте составной номер из данных документа. Если номер чисто технический — используйте GUID. Мы рекомендуем избегать блокировок всего регистра накопления, так как это критически снижает масштабируемость системы.