При разработке в среде 1С:Предприятие 8.3 программисты часто сталкиваются с ситуацией, когда стандартные механизмы нумерации не подходят под требования бизнеса (например, нужна нумерация в пределах дня для справочника). Попытка реализовать нумерацию вручную через запрос Количество() + 1 или МАКСИМУМ(Код) неизбежно приводит к появлению дублей, особенно если в базе работают одновременно десятки или сотни пользователей. Рассмотрим подробнее, почему это происходит и как правильно решить эту задачу с помощью управляемых блокировок.
Проанализируем типичную ситуацию. Когда два пользователя нажимают кнопку «Записать», код в их сеансах выполняется практически одновременно. Если мы просто выполняем запрос для получения нового номера, происходит следующее:
Даже если вы используете подписки на события или проверки в независимых регистрах сведений, без явного управления блокировками на уровне СУБД проблема не исчезнет. Обычный запрос ВЫБРАТЬ в режиме управляемых блокировок не блокирует данные от чтения другими пользователями.
Разберем основной способ решения проблемы через использование объекта БлокировкаДанных. Суть метода заключается в том, чтобы «застолбить» за собой право на определение номера еще до того, как мы его вычислим. Это заставит другие сеансы ждать, пока наша транзакция не завершится.
Рассмотрим пример кода, который необходимо разместить в модуле объекта (в обработчике ПередЗаписью) или в процедуре программного создания элемента:
УстановитьПривилегированныйРежим(Истина);
НачатьТранзакцию();
// Создаем объект блокировки
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("Справочник.ВашСправочник");
// Если нумерация идет в разрезе даты, можно установить пространство блокировки по дате,
// но чаще блокируют весь объект или вспомогательный регистр
Блокировка.Заблокировать();
// Теперь, когда мы уверены, что никто другой не выполняет этот код одновременно с нами,
// вычисляем новый номер запросом
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ЕСТЬNULL(МАКСИМУМ(ВашСправочник.Код), 0) + 1 КАК НовыйНомер
|ИЗ
| Справочник.ВашСправочник КАК ВашСправочник
|ГДЕ
| ВашСправочник.ДатаСоздания = &ТекущаяДата";
Запрос.УстановитьПараметр("ТекущаяДата", НачалоДня(ТекущаяДата()));
РезультатЗапроса = Запрос.Выполнить();
Если Не РезультатЗапроса.Пустой() Тогда
Выборка = РезультатЗапроса.Выбрать();
Выборка.Следующий();
Объект.Код = Выборка.НовыйНомер;
КонецЕсли;
// После этого выполняется стандартная запись объекта
ЗафиксироватьТранзакцию();
Важный нюанс: Блокировка всей таблицы справочника может снизить производительность при 150 пользователях. Поэтому рекомендуется блокировать не весь справочник, а запись в специальном вспомогательном регистре-счетчике.
Посмотрим на более изящное решение. Создадим независимый регистр сведений НумераторСправочников с измерениями ИмяСправочника и ПериодНумерации (например, дата дня), и ресурсом ПоследнийНомер. Это позволит нам накладывать узкую блокировку только на одну строку регистра, не мешая пользователям работать с другими данными справочника.
Проанализируем алгоритм работы:
БлокировкаДанных на одну конкретную запись регистра НумераторСправочников по ключу (название справочника + текущий день).ПоследнийНомер, увеличиваем его на 1.
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("РегистрСведений.НумераторСправочников");
ЭлементБлокировки.УстановитьЗначение("ИмяСправочника", "ВашСправочник");
ЭлементБлокировки.УстановитьЗначение("ПериодНумерации", НачалоДня(ТекущаяДата()));
Блокировка.Заблокировать();
// Чтение и запись регистра счетчика...
// (код менеджера записи аналогичен примеру в полезных сообщениях форума)
Рассмотрим ситуацию с точки зрения типовых методологий 1С. Если вам требуется строгая нумерация в пределах дня, возможно, использование Справочника было не лучшим решением. В системе 1С для периодической нумерации предназначены Документы.
Если изменить тип объекта нельзя, проанализируем возможность использования стандартного механизма префиксов. Вы можете программно устанавливать префикс кода, зависящий от даты (например, "20231025-"). Тогда платформа сама будет контролировать уникальность в рамках этого префикса, используя внутренние оптимизированные механизмы, и вам не придется писать сложные блокировки вручную.
Поскольку вы используете достаточно старую версию SQL Server и платформы, обратите внимание на следующие моменты:
Lock request time out.Количество() в запросах для получения номера. На больших таблицах (сотни тысяч записей) это вызывает полное сканирование таблицы (Table Scan), что крайне медленно. Используйте только МАКСИМУМ() по индексированным полям.ТранзакцияАктивна().Применяя эти методы, вы гарантируете целостность данных даже при очень высокой интенсивности работы пользователей, исключая вероятность появления «необъяснимых» дублей.