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